From cb6ad76ae6867c1ed8a8e3a7d2fd2796e1724d44 Mon Sep 17 00:00:00 2001 From: 109000102 Date: Thu, 20 Mar 2025 16:01:22 +0800 Subject: [PATCH] lab3 --- lab3/README.md | 29 ++++++++++++++++++++ lab3/main.js | 34 +++++++++++++++++++++++ lab3/main_test.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++ lab3/validate.sh | 38 +++++++++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 lab3/README.md create mode 100644 lab3/main.js create mode 100644 lab3/main_test.js create mode 100755 lab3/validate.sh diff --git a/lab3/README.md b/lab3/README.md new file mode 100644 index 0000000..b591ab8 --- /dev/null +++ b/lab3/README.md @@ -0,0 +1,29 @@ +# Lab3 + +## Introduction + +In this lab, you will write unit tests for functions implemented in `main.js`. You can learn how to use classes and functions in it by uncommenting the code in it. (But remember don't commit them on GitHub) + +## Preparation (Important!!!) + +1. Sync fork on GitHub +2. `git checkout -b lab3` (**NOT** your student ID !!!) + +## Requirement + +1. (40%) Write test cases in `main_test.js` and achieve 100% code coverage. +2. (30%) For each function, parameterize their testcases to test the error-results. +3. (30%) For each function, use at least 3 parameterized testcases to test the non-error-results. + +You can run `validate.sh` in your local to test if you satisfy the requirements. + +Please note that you must not alter files other than `main_test.js`. You will get 0 points if + +1. you modify other files to achieve requirements. +2. you can't pass all CI on your PR. + +## Submission + +You need to open a pull request to your branch (e.g. 312XXXXXX, your student number) and contain the code that satisfies the abovementioned requirements. + +Moreover, please submit the URL of your PR to E3. Your submission will only be accepted when you present at both places. diff --git a/lab3/main.js b/lab3/main.js new file mode 100644 index 0000000..cee5de7 --- /dev/null +++ b/lab3/main.js @@ -0,0 +1,34 @@ +class Calculator { + exp(x) { + if (!Number.isFinite(x)) { + throw Error('unsupported operand type'); + } + const result = Math.exp(x); + if (result === Infinity) { + throw Error('overflow'); + } + return result; + } + + log(x) { + if (!Number.isFinite(x)) { + throw Error('unsupported operand type'); + } + const result = Math.log(x); + if (result === -Infinity) { + throw Error('math domain error (1)'); + } + if (Number.isNaN(result)) { + throw Error('math domain error (2)'); + } + return result; + } +} + +// const calculator = new Calculator(); +// console.log(calculator.exp(87)); +// console.log(calculator.log(48763)); + +module.exports = { + Calculator +}; \ No newline at end of file diff --git a/lab3/main_test.js b/lab3/main_test.js new file mode 100644 index 0000000..369b58a --- /dev/null +++ b/lab3/main_test.js @@ -0,0 +1,70 @@ +const {describe, it} = require('node:test'); +const assert = require('assert'); +const { Calculator } = require('./main'); + +describe('Calculator', () => { + describe('exp', () => { + const calculator = new Calculator(); + + it('should throw "unsupported operand type" for non-finite numbers', () => { + const invalidInputs = [NaN, Infinity, -Infinity, 'string', null, undefined]; + invalidInputs.forEach(input => { + assert.throws(() => calculator.exp(input), Error('unsupported operand type')); + }); + }); + + it('should throw "overflow" for large numbers', () => { + const largeInputs = [1000, 10000, 100000]; + largeInputs.forEach(input => { + assert.throws(() => calculator.exp(input), Error('overflow')); + }); + }); + + it('should return correct exp value for valid inputs', () => { + const testCases = [ + { input: 0, expected: 1 }, + { input: 1, expected: Math.exp(1) }, + { input: -1, expected: Math.exp(-1) }, + ]; + testCases.forEach(({ input, expected }) => { + assert.strictEqual(calculator.exp(input), expected); + }); + }); + }); + + describe('log', () => { + const calculator = new Calculator(); + + it('should throw "unsupported operand type" for non-finite numbers', () => { + const invalidInputs = [NaN, Infinity, -Infinity, 'string', null, undefined]; + invalidInputs.forEach(input => { + assert.throws(() => calculator.log(input), Error('unsupported operand type')); + }); + }); + + it('should throw "math domain error (1)" for zero', () => { + const invalidInputs = [0]; + invalidInputs.forEach(input => { + assert.throws(() => calculator.log(input), Error('math domain error (1)')); + }); + }); + + it('should throw "math domain error (2)" for finite negative numbers', () => { + const invalidInputs = [-1, -5, -100]; + invalidInputs.forEach(input => { + assert.throws(() => calculator.log(input), Error('math domain error (2)')); + }); + }); + + it('should return correct log value for valid inputs', () => { + const testCases = [ + { input: 1, expected: 0 }, + { input: Math.E, expected: 1 }, + { input: 10, expected: Math.log(10) }, + ]; + testCases.forEach(({ input, expected }) => { + assert.strictEqual(calculator.log(input), expected); + }); + }); + }); +}); \ No newline at end of file diff --git a/lab3/validate.sh b/lab3/validate.sh new file mode 100755 index 0000000..7a758fb --- /dev/null +++ b/lab3/validate.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "main.js" && $file != "main_test.js" && $file != "README.md" && $file != "validate.sh" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +node=$(which node) +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab3-XXXXXXXXXX) + +cd $tmp_dir + +rm -rf * +cp $solution_path/*.js . +result=$($"node" --test --experimental-test-coverage) ; ret=$? +if [ $ret -ne 0 ] ; then + echo "[!] testing fails" + exit 1 +else + coverage=$(echo "$result" | grep 'all files' | awk -F '|' '{print $2}' | sed 's/ //g') + if (( $(echo "$coverage < 100" | bc -l) )); then + echo "[!] Coverage is only $coverage%" + exit 1 + else + echo "[V] Coverage is 100%" + fi +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: \ No newline at end of file