From 15a32e92a64306bfa7494eb85b70ca3b72fed624 Mon Sep 17 00:00:00 2001 From: ShangHungWan Date: Wed, 13 Mar 2024 00:21:39 +0800 Subject: [PATCH 01/15] fix: checklist-check workflow's regex --- .github/workflows/PR.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index 10f942ec..c4aad3f8 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -49,12 +49,12 @@ jobs: const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number }); const body = pr.data.body; - const checkboxes = body.match(/\- \[[x ]\]/g); + const checkboxes = body.match(/^\ ?(-|*) \[[x ]\]/gi); if (!checkboxes || checkboxes.length !== 5) { core.setFailed('The PR description must contain exactly 5 checkboxes.'); } - const unchecked = body.match(/\- \[ \]/g); + const unchecked = body.match(/^\ ?(-|*) \[ \]/g); if (unchecked && unchecked.length > 0) { core.setFailed(`There are ${unchecked.length} unchecked items in the PR description.`); } From a1dc1e283fd09062b0707a522e1d8c38a289a811 Mon Sep 17 00:00:00 2001 From: ShangHungWan Date: Wed, 13 Mar 2024 00:22:46 +0800 Subject: [PATCH 02/15] fix: install node from setup-node instead of script installation --- .github/workflows/lab1.yml | 7 +++---- .github/workflows/lab2.yml | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lab1.yml b/.github/workflows/lab1.yml index 49a5d578..befe2593 100644 --- a/.github/workflows/lab1.yml +++ b/.github/workflows/lab1.yml @@ -16,10 +16,9 @@ jobs: - uses: actions/checkout@v1 with: fetch-depth: 1 - - name: dependency (ubuntu) - run: | - curl -fsSL https://deb.nodesource.com/setup_21.x | sudo -E bash - &&\ - sudo apt-get install -y nodejs + - uses: actions/setup-node@v4 + with: + node-version: latest - name: grading run: | cd lab1 diff --git a/.github/workflows/lab2.yml b/.github/workflows/lab2.yml index 2cb76fa7..f62e24e6 100644 --- a/.github/workflows/lab2.yml +++ b/.github/workflows/lab2.yml @@ -16,10 +16,9 @@ jobs: - uses: actions/checkout@v1 with: fetch-depth: 1 - - name: dependency (ubuntu) - run: | - curl -fsSL https://deb.nodesource.com/setup_21.x | sudo -E bash - &&\ - sudo apt-get install -y nodejs + - uses: actions/setup-node@v4 + with: + node-version: latest - name: grading run: | cd lab2 From 656d908d0eb457758e7fa80ef737eab823093ebc Mon Sep 17 00:00:00 2001 From: ShangHungWan Date: Wed, 13 Mar 2024 20:48:38 +0800 Subject: [PATCH 03/15] feat: fix buggy regex and add changed-files-check in pr.yml --- .github/workflows/PR.yml | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index c4aad3f8..92297ece 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -16,7 +16,7 @@ jobs: const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number }); const title = pr.data.title; const labRegex = /\[LAB(\d+)\]/; - const titleRegex = /^\[LAB\d+\] [\da-zA-Z]+$/; + const titleRegex = /^\[LAB\d+\] [a-zA-Z]?\d+$/; if (!titleRegex.test(title)) { core.setFailed('PR title does not match the required format. Please use the format [LAB#] student#.'); @@ -49,12 +49,33 @@ jobs: const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number }); const body = pr.data.body; - const checkboxes = body.match(/^\ ?(-|*) \[[x ]\]/gi); + const checkboxes = body.match(/^ ?(-|\*) \[[x ]\]/gi); if (!checkboxes || checkboxes.length !== 5) { core.setFailed('The PR description must contain exactly 5 checkboxes.'); } - const unchecked = body.match(/^\ ?(-|*) \[ \]/g); + const unchecked = body.match(/^ ?(-|\*) \[ \]/g); if (unchecked && unchecked.length > 0) { core.setFailed(`There are ${unchecked.length} unchecked items in the PR description.`); } + changed-files-check: + runs-on: ubuntu-latest + steps: + - name: Check no changes other than specific files + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { owner, repo, number: issue_number } = context.issue; + const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number }); + const files = await github.rest.pulls.listFiles({ owner, repo, pull_number: issue_number }); + const changedFiles = files.data.map((file) => file.filename); + echo changedFiles; + + const allowedFiles = [ + 'lab1/main_test.js', + 'lab2/main_test.js', + ]; + if (!changedFiles.every((file) => allowedFiles.includes(file))) { + core.setFailed('The PR contains changes to files other than the allowed files.'); + } From 68ee1986dd6c6cae2a5a9ddaabc217f1f3c0e0b5 Mon Sep 17 00:00:00 2001 From: ShangHungWan Date: Wed, 13 Mar 2024 21:24:25 +0800 Subject: [PATCH 04/15] feat: use one autograding workflow instead of each lab's --- .github/workflows/lab-autograding.yml | 56 +++++++++++++++++++++++++++ .github/workflows/lab1.yml | 25 ------------ .github/workflows/lab2.yml | 25 ------------ 3 files changed, 56 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/lab-autograding.yml delete mode 100644 .github/workflows/lab1.yml delete mode 100644 .github/workflows/lab2.yml diff --git a/.github/workflows/lab-autograding.yml b/.github/workflows/lab-autograding.yml new file mode 100644 index 00000000..2cd4a9df --- /dev/null +++ b/.github/workflows/lab-autograding.yml @@ -0,0 +1,56 @@ +name: Autograding + +on: + pull_request: + types: [labeled, synchronize, opened, reopened, ready_for_review] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04] + fail-fast: false + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 1 + - uses: actions/setup-node@v4 + with: + node-version: latest + - name: Extract lab number and Check no changes other than specific files + uses: actions/github-script@v5 + id: lab + with: + result-encoding: string + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { owner, repo, number: issue_number } = context.issue; + const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number }); + const labels = pr.data.labels; + const lab = labels.find((label) => label.name.startsWith('lab')); + if (!lab) { + core.setFailed('No lab label found on the PR.'); + return { number: 0 }; + } + const labNumberMatch = lab.name.match(/lab(\d+)/); + if (!labNumberMatch) { + core.setFailed('Invalid lab label found on the PR.'); + return { number: 0 }; + } + const labNumber = labNumberMatch[1]; + console.log(`Lab number: ${labNumber}`) + + const files = await github.rest.pulls.listFiles({ owner, repo, pull_number: issue_number }); + const changedFiles = files.data.map((file) => file.filename); + const allowedFiles = [ + `lab${labNumber}/main_test.js`, + ]; + if (!changedFiles.every((file) => allowedFiles.includes(file))) { + core.setFailed('The PR contains changes to files other than the allowed files.'); + } + return labNumber; + - name: Grading + run: | + cd lab${{ steps.lab.outputs.result }} + ./validate.sh diff --git a/.github/workflows/lab1.yml b/.github/workflows/lab1.yml deleted file mode 100644 index befe2593..00000000 --- a/.github/workflows/lab1.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: lab1 autograding - -on: - pull_request: - types: [labeled, synchronize, opened, reopened, ready_for_review] - -jobs: - build: - if: contains(github.event.pull_request.labels.*.name, 'lab1') - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-22.04] - fail-fast: false - steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - - uses: actions/setup-node@v4 - with: - node-version: latest - - name: grading - run: | - cd lab1 - ./validate.sh diff --git a/.github/workflows/lab2.yml b/.github/workflows/lab2.yml deleted file mode 100644 index f62e24e6..00000000 --- a/.github/workflows/lab2.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: lab2 autograding - -on: - pull_request: - types: [labeled, synchronize, opened, reopened, ready_for_review] - -jobs: - build: - if: contains(github.event.pull_request.labels.*.name, 'lab2') - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-22.04] - fail-fast: false - steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - - uses: actions/setup-node@v4 - with: - node-version: latest - - name: grading - run: | - cd lab2 - ./validate.sh From d494aaefaf7c0139ec67af5f8b28250d1e7aaf33 Mon Sep 17 00:00:00 2001 From: ytchao0234 Date: Wed, 13 Mar 2024 21:51:11 +0800 Subject: [PATCH 05/15] add: lab2 test --- lab2/main_test.js | 120 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/lab2/main_test.js b/lab2/main_test.js index 5034468e..f4262286 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -1,6 +1,122 @@ -const test = require('node:test'); +const {mock, test} = require('node:test'); const assert = require('assert'); const { Application, MailSystem } = require('./main'); // TODO: write your tests here -// Remember to use Stub, Mock, and Spy when necessary \ No newline at end of file +// Remember to use Stub, Mock, and Spy when necessary + +const fs = require('fs'); +const filePath = 'name_list.txt'; + +function writeFile(content) { + return new Promise((resolve) => { + fs.writeFile(filePath, content, resolve); + }); +} + +function unlink() { + return new Promise((resolve) => { + fs.unlink(filePath, resolve); + }); +} + +test('Test Application\'s getNames', async (t) => { + const fn = t.mock.fn(writeFile); + await fn('John\nDoe'); + + let myApp = new Application(); + await fn('John\nDoe'); + assert.deepEqual(await myApp.getNames(), [['John', 'Doe'], []]); + + fn.mock.mockImplementation(unlink); + await fn(); +}); + +test('Test Application\'s getRandomPerson', async (t) => { + const fn = t.mock.fn(writeFile); + await fn('John\nDoe'); + let myApp = new Application(); + myApp.people = ['John', 'Doe']; + + const floor = t.mock.method(Math, 'floor'); + const random = t.mock.method(Math, 'random'); + assert.strictEqual(floor.mock.callCount(), 0); + assert.strictEqual(random.mock.callCount(), 0); + + for (let i = 0; i < 20; i++) { + result = myApp.getRandomPerson(); + assert.strictEqual(floor.mock.callCount(), i + 1); + assert.strictEqual(random.mock.callCount(), i + 1); + + resultIndex = floor.mock.calls[i].result; + assert.strictEqual(myApp.people.includes(result), true); + assert.strictEqual(myApp.people[resultIndex], result); + } + fn.mock.mockImplementation(unlink); + await fn(); +}); + +test('Test Application\'s selectNextPerson', async (t) => { + const fn = t.mock.fn(writeFile); + await fn('John\nDoe'); + myApp = new Application(); + myApp.people = ['John', 'Doe', 'Amy', 'Mark', 'Jane']; + + for (let i = 0; i < myApp.people.length + 1; i++) { + count = myApp.selected.length; + result = myApp.selectNextPerson(); + + if (count < myApp.people.length) { + assert.strictEqual(myApp.selected.length, count + 1); + } + else + { + assert.strictEqual(myApp.selected.length, count); + } + } + fn.mock.mockImplementation(unlink); + await fn(); +}); + +test('Test Application\'s notifySelected', async (t) => { + const fn = t.mock.fn(writeFile); + await fn('John\nDoe'); + myApp = new Application(); + myApp.selected = ['John', 'Doe', 'Amy', 'Mark', 'Jane']; + myApp.mailSystem = new MailSystem(); + + const notifySelected = t.mock.method(myApp, 'notifySelected'); + const send = t.mock.method(myApp.mailSystem, 'send'); + const write = t.mock.method(myApp.mailSystem, 'write'); + + assert.strictEqual(notifySelected.mock.calls.length, 0); + assert.strictEqual(send.mock.calls.length, 0); + assert.strictEqual(write.mock.calls.length, 0); + + myApp.notifySelected(); + assert.strictEqual(notifySelected.mock.callCount(), 1); + assert.strictEqual(send.mock.calls.length, myApp.selected.length); + assert.strictEqual(write.mock.calls.length, myApp.selected.length); + + fn.mock.mockImplementation(unlink); + await fn(); +}); + +test('Test MailSystem\'s write', (t) => { + myMailSystem = new MailSystem(); + assert.strictEqual(myMailSystem.write('John'), 'Congrats, John!'); +}); + +test('Test MailSystem\'s send', (t) => { + myMailSystem = new MailSystem(); + const random = t.mock.method(Math, 'random'); + assert.strictEqual(random.mock.callCount(), 0); + + for (let i = 0; i < 20; i++) { + result = myMailSystem.send(null, null); + assert.strictEqual(random.mock.callCount(), i + 1); + + const call = random.mock.calls[i]; + assert.strictEqual(result, call.result > 0.5); + } +}); \ No newline at end of file From 7d4c24aaa2f81c64eec805aa82fe99cecdc1a290 Mon Sep 17 00:00:00 2001 From: AlaRduTP Date: Thu, 14 Mar 2024 00:24:07 +0800 Subject: [PATCH 06/15] feat: rewrite source branch's naming rules --- .github/workflows/PR.yml | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index 92297ece..5dbfdfa7 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -15,27 +15,31 @@ jobs: const { owner, repo, number: issue_number } = context.issue; const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number }); const title = pr.data.title; - const labRegex = /\[LAB(\d+)\]/; - const titleRegex = /^\[LAB\d+\] [a-zA-Z]?\d+$/; - if (!titleRegex.test(title)) { - core.setFailed('PR title does not match the required format. Please use the format [LAB#] student#.'); + const titleRegex = /^\[LAB(\d+)\] [a-zA-Z]?\d+$/; + const match = title.match(titleRegex); + + const labNumberStr = undefined; + if (match) { + labNumberStr = match[1]; + } else { + core.setFailed('PR title does not match the required format. Please use the format: [LAB#] .'); } - if (pr.data.head.ref !== pr.data.base.ref) { - core.setFailed('The source branch and target branch must be the same.'); + const labelToAdd = `lab${labNumberStr}`; + if (labNumberStr) { + await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [labelToAdd] }); } if (pr.data.base.ref === 'main') { core.setFailed('The target branch cannot be main.'); } - const match = title.match(labRegex); - if (match) { - const labelToAdd = 'lab' + match[1]; - await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [labelToAdd] }); - } else { - core.setFailed('No match found in PR title. Please add a label in the format [LAB#] to the PR title.'); + if (labNumberStr < 3 && pr.data.head.ref !== pr.data.base.ref) { + core.setFailed('The source branch and target branch must be the same.'); + } + if (labNumberStr >= 3 && pr.data.head.ref !== labelToAdd) { + core.setFailed(`The source branch must be '${labelToAdd}'`); } checklist-check: runs-on: ubuntu-latest From d44bbe34c41c0e6f1095f4076a5ea3cd1ac8d539 Mon Sep 17 00:00:00 2001 From: AlaRduTP Date: Thu, 14 Mar 2024 00:58:21 +0800 Subject: [PATCH 07/15] feat: use pull_request_target instead of pull_request --- .github/workflows/lab-autograding.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lab-autograding.yml b/.github/workflows/lab-autograding.yml index 2cd4a9df..918d29b8 100644 --- a/.github/workflows/lab-autograding.yml +++ b/.github/workflows/lab-autograding.yml @@ -1,7 +1,7 @@ name: Autograding on: - pull_request: + pull_request_target: types: [labeled, synchronize, opened, reopened, ready_for_review] jobs: From f02c1ea1384ad0419bc30ebc15ca1e5e400e9029 Mon Sep 17 00:00:00 2001 From: AlaRduTP Date: Thu, 14 Mar 2024 01:57:31 +0800 Subject: [PATCH 08/15] fix: multiple errors in workflows --- .github/workflows/PR.yml | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index 5dbfdfa7..54b7d6b2 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -19,7 +19,7 @@ jobs: const titleRegex = /^\[LAB(\d+)\] [a-zA-Z]?\d+$/; const match = title.match(titleRegex); - const labNumberStr = undefined; + let labNumberStr = undefined; if (match) { labNumberStr = match[1]; } else { @@ -53,33 +53,12 @@ jobs: const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number }); const body = pr.data.body; - const checkboxes = body.match(/^ ?(-|\*) \[[x ]\]/gi); + const checkboxes = body.match(/^ ?(-|\*) \[[x ]\]/gmi); if (!checkboxes || checkboxes.length !== 5) { core.setFailed('The PR description must contain exactly 5 checkboxes.'); } - const unchecked = body.match(/^ ?(-|\*) \[ \]/g); + const unchecked = body.match(/^ ?(-|\*) \[ \]/gm); if (unchecked && unchecked.length > 0) { - core.setFailed(`There are ${unchecked.length} unchecked items in the PR description.`); - } - changed-files-check: - runs-on: ubuntu-latest - steps: - - name: Check no changes other than specific files - uses: actions/github-script@v5 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { owner, repo, number: issue_number } = context.issue; - const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number }); - const files = await github.rest.pulls.listFiles({ owner, repo, pull_number: issue_number }); - const changedFiles = files.data.map((file) => file.filename); - echo changedFiles; - - const allowedFiles = [ - 'lab1/main_test.js', - 'lab2/main_test.js', - ]; - if (!changedFiles.every((file) => allowedFiles.includes(file))) { - core.setFailed('The PR contains changes to files other than the allowed files.'); + core.setFailed(`There are ${unchecked.length} unchecked item(s) in the PR description.`); } From 2d4b5d3cf5680ff2d8f4a19f887f1d0e931743dc Mon Sep 17 00:00:00 2001 From: AlaRduTP Date: Thu, 14 Mar 2024 02:02:18 +0800 Subject: [PATCH 09/15] feat: merge and squash script --- scripts/merge-all.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 scripts/merge-all.sh diff --git a/scripts/merge-all.sh b/scripts/merge-all.sh new file mode 100755 index 00000000..a3be7f90 --- /dev/null +++ b/scripts/merge-all.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +if [ $# -ne 1 ]; then + echo "./merge-all.sh " + exit 1 +fi + +git fetch origin + +for branch in $(git branch -r | grep -v HEAD); do + # Remove the "origin/" prefix + branch=${branch#origin/} + + if [[ "$branch" != "main" ]]; then + git checkout "$branch" + if [[ $? -ne 0 ]]; then + echo "Checkout failed for branch $branch" + exit 1 + fi + git merge --squash main + if [[ $? -ne 0 ]]; then + echo "Merge failed for branch $branch" + exit 1 + fi + git commit -m "$1" + fi +done + +git checkout main \ No newline at end of file From 91270f22e4625e0f70f9ba322ec21bbe0cfce9b8 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 14 Mar 2024 13:17:48 +0800 Subject: [PATCH 10/15] fix: didn't checkout when grading --- .github/workflows/lab-autograding.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lab-autograding.yml b/.github/workflows/lab-autograding.yml index 918d29b8..502b9631 100644 --- a/.github/workflows/lab-autograding.yml +++ b/.github/workflows/lab-autograding.yml @@ -12,12 +12,12 @@ jobs: os: [ubuntu-22.04] fail-fast: false steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - uses: actions/setup-node@v4 with: node-version: latest + - uses: actions/checkout@v1 + with: + fetch-depth: 1 - name: Extract lab number and Check no changes other than specific files uses: actions/github-script@v5 id: lab @@ -50,6 +50,9 @@ jobs: core.setFailed('The PR contains changes to files other than the allowed files.'); } return labNumber; + - uses: actions/checkout@v1 + with: + fetch-depth: 1 - name: Grading run: | cd lab${{ steps.lab.outputs.result }} From 2c9fd78581c28c9b3d7d98008cd1eac4818a4312 Mon Sep 17 00:00:00 2001 From: AlaRduTP Date: Thu, 14 Mar 2024 13:55:59 +0800 Subject: [PATCH 11/15] feat: add lab3 --- lab3/main.js | 34 ++++++++++++++++++++++++++++++++++ lab3/main_test.js | 5 +++++ lab3/validate.sh | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 lab3/main.js create mode 100644 lab3/main_test.js create mode 100755 lab3/validate.sh diff --git a/lab3/main.js b/lab3/main.js new file mode 100644 index 00000000..cee5de7f --- /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 00000000..e6d6414e --- /dev/null +++ b/lab3/main_test.js @@ -0,0 +1,5 @@ +const {describe, it} = require('node:test'); +const assert = require('assert'); +const { Calculator } = require('./main'); + +// TODO: write your tests here diff --git a/lab3/validate.sh b/lab3/validate.sh new file mode 100755 index 00000000..7a758fb3 --- /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 From f6b8b88b075b01d8cc3e4b3a26e7d22393693d7d Mon Sep 17 00:00:00 2001 From: AlaRduTP Date: Thu, 14 Mar 2024 13:56:26 +0800 Subject: [PATCH 12/15] doc: add lab3 requirements --- lab3/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 lab3/README.md diff --git a/lab3/README.md b/lab3/README.md new file mode 100644 index 00000000..f95c2ca0 --- /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. 311XXXXXX, 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. From 06329c2ee1e95dc713abad6cf49c78d9b847dfa6 Mon Sep 17 00:00:00 2001 From: AlaRduTP Date: Thu, 14 Mar 2024 14:21:04 +0800 Subject: [PATCH 13/15] fix: wrong GITHUB SHA in workflows --- .github/workflows/lab-autograding.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lab-autograding.yml b/.github/workflows/lab-autograding.yml index 502b9631..52f68ea6 100644 --- a/.github/workflows/lab-autograding.yml +++ b/.github/workflows/lab-autograding.yml @@ -12,12 +12,13 @@ jobs: os: [ubuntu-22.04] fail-fast: false steps: + - uses: actions/checkout@v4 + with: + ref: "${{ github.event.pull_request.merge_commit_sha }}" + fetch-depth: 1 - uses: actions/setup-node@v4 with: node-version: latest - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - name: Extract lab number and Check no changes other than specific files uses: actions/github-script@v5 id: lab @@ -50,9 +51,6 @@ jobs: core.setFailed('The PR contains changes to files other than the allowed files.'); } return labNumber; - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - name: Grading run: | cd lab${{ steps.lab.outputs.result }} From ebef053faafb840b57ebd7c042ec7b42f792230e Mon Sep 17 00:00:00 2001 From: AlaRduTP Date: Thu, 14 Mar 2024 16:07:59 +0800 Subject: [PATCH 14/15] hotfix: remove changed files --- .github/workflows/lab-autograding.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lab-autograding.yml b/.github/workflows/lab-autograding.yml index 52f68ea6..595dc5ce 100644 --- a/.github/workflows/lab-autograding.yml +++ b/.github/workflows/lab-autograding.yml @@ -47,9 +47,9 @@ jobs: const allowedFiles = [ `lab${labNumber}/main_test.js`, ]; - if (!changedFiles.every((file) => allowedFiles.includes(file))) { - core.setFailed('The PR contains changes to files other than the allowed files.'); - } + // if (!changedFiles.every((file) => allowedFiles.includes(file))) { + // core.setFailed('The PR contains changes to files other than the allowed files.'); + // } return labNumber; - name: Grading run: | From a4e639ff59077d20df23fc32c5a250a12fcf8e8d Mon Sep 17 00:00:00 2001 From: ytchao0234 Date: Fri, 15 Mar 2024 01:03:36 +0800 Subject: [PATCH 15/15] fix: lab2 test --- lab2/main_test.js | 77 +++++++++++++++-------------------------------- 1 file changed, 24 insertions(+), 53 deletions(-) diff --git a/lab2/main_test.js b/lab2/main_test.js index f4262286..32ea43a8 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -1,66 +1,40 @@ const {mock, test} = require('node:test'); const assert = require('assert'); +const fs = require('fs'); + +mock.method(fs, 'readFile', (path, encoding, callback) => { + callback(null, 'John\nDoe'); +}); + const { Application, MailSystem } = require('./main'); // TODO: write your tests here // Remember to use Stub, Mock, and Spy when necessary -const fs = require('fs'); -const filePath = 'name_list.txt'; - -function writeFile(content) { - return new Promise((resolve) => { - fs.writeFile(filePath, content, resolve); - }); -} - -function unlink() { - return new Promise((resolve) => { - fs.unlink(filePath, resolve); - }); -} - test('Test Application\'s getNames', async (t) => { - const fn = t.mock.fn(writeFile); - await fn('John\nDoe'); - let myApp = new Application(); - await fn('John\nDoe'); assert.deepEqual(await myApp.getNames(), [['John', 'Doe'], []]); - - fn.mock.mockImplementation(unlink); - await fn(); }); test('Test Application\'s getRandomPerson', async (t) => { - const fn = t.mock.fn(writeFile); - await fn('John\nDoe'); let myApp = new Application(); - myApp.people = ['John', 'Doe']; + await new Promise((resolve) => { setTimeout(resolve, 1); }); const floor = t.mock.method(Math, 'floor'); const random = t.mock.method(Math, 'random'); - assert.strictEqual(floor.mock.callCount(), 0); - assert.strictEqual(random.mock.callCount(), 0); + let length = myApp.people.length; - for (let i = 0; i < 20; i++) { + for (let i = 0; i < length; i++) { + Math.random.mock.mockImplementation(() => i / length); result = myApp.getRandomPerson(); - assert.strictEqual(floor.mock.callCount(), i + 1); - assert.strictEqual(random.mock.callCount(), i + 1); - - resultIndex = floor.mock.calls[i].result; assert.strictEqual(myApp.people.includes(result), true); - assert.strictEqual(myApp.people[resultIndex], result); + assert.strictEqual(result, myApp.people[i]); } - fn.mock.mockImplementation(unlink); - await fn(); }); test('Test Application\'s selectNextPerson', async (t) => { - const fn = t.mock.fn(writeFile); - await fn('John\nDoe'); myApp = new Application(); - myApp.people = ['John', 'Doe', 'Amy', 'Mark', 'Jane']; + await new Promise((resolve) => { setTimeout(resolve, 1); }); for (let i = 0; i < myApp.people.length + 1; i++) { count = myApp.selected.length; @@ -68,21 +42,19 @@ test('Test Application\'s selectNextPerson', async (t) => { if (count < myApp.people.length) { assert.strictEqual(myApp.selected.length, count + 1); + assert.strictEqual(myApp.selected.includes(result), true); } else { assert.strictEqual(myApp.selected.length, count); } } - fn.mock.mockImplementation(unlink); - await fn(); }); test('Test Application\'s notifySelected', async (t) => { - const fn = t.mock.fn(writeFile); - await fn('John\nDoe'); myApp = new Application(); - myApp.selected = ['John', 'Doe', 'Amy', 'Mark', 'Jane']; + await new Promise((resolve) => { setTimeout(resolve, 1); }); + myApp.selected = myApp.people; myApp.mailSystem = new MailSystem(); const notifySelected = t.mock.method(myApp, 'notifySelected'); @@ -97,9 +69,6 @@ test('Test Application\'s notifySelected', async (t) => { assert.strictEqual(notifySelected.mock.callCount(), 1); assert.strictEqual(send.mock.calls.length, myApp.selected.length); assert.strictEqual(write.mock.calls.length, myApp.selected.length); - - fn.mock.mockImplementation(unlink); - await fn(); }); test('Test MailSystem\'s write', (t) => { @@ -112,11 +81,13 @@ test('Test MailSystem\'s send', (t) => { const random = t.mock.method(Math, 'random'); assert.strictEqual(random.mock.callCount(), 0); - for (let i = 0; i < 20; i++) { - result = myMailSystem.send(null, null); - assert.strictEqual(random.mock.callCount(), i + 1); - - const call = random.mock.calls[i]; - assert.strictEqual(result, call.result > 0.5); - } + Math.random.mock.mockImplementation(() => 0.6); + result = myMailSystem.send(null, null); + assert.strictEqual(random.mock.callCount(), 1); + assert.strictEqual(result, true); + + Math.random.mock.mockImplementation(() => 0.4); + result = myMailSystem.send(null, null); + assert.strictEqual(random.mock.callCount(), 2); + assert.strictEqual(result, false); }); \ No newline at end of file