From 31a0a96fd5cf139f0991e57d673e02accf1fd6cd Mon Sep 17 00:00:00 2001 From: TaiYou Date: Thu, 7 Mar 2024 00:38:29 +0800 Subject: [PATCH 01/10] fix: typo in lab1 README --- lab1/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab1/README.md b/lab1/README.md index 779166ca..78ecd97a 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -2,7 +2,7 @@ ## 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 `main.js.` +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. ## Requirement From 818f02a0c03e1d9c306db2b5f22baa253201a850 Mon Sep 17 00:00:00 2001 From: TaiYou Date: Thu, 7 Mar 2024 00:46:41 +0800 Subject: [PATCH 02/10] doc: update description in PR template --- .github/pull_request_template.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ab8eabd5..88464606 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,17 +1,17 @@ ## Description - + --- - + - [ ] A clear title (name your pr "[LAB{lab_number}] {your_student_id}") - [ ] A meaningful message for PR, as well as its commits - [ ] From your specific branch (***not main or other's branch***) merging to your branch - [ ] Excluding any irrelevant files, such as binaries, text files, or dot files -- [ ] Passing tests/CI +- [ ] Passing all CI (You should check it first to pass one of the validations in CI. However, you need to make sure your PR passes all CI after you submit it.) From c4809822cc66aa6455e702a7449266a9d3e318ae Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 7 Mar 2024 13:52:37 +0800 Subject: [PATCH 03/10] feat: improve workflows --- .github/pull_request_template.md | 2 +- .github/workflows/PR.yml | 60 ++++++++++++++++++++++++++++++++ .github/workflows/lab1.yml | 7 ++-- .github/workflows/lab2.yml | 26 ++++++++++++++ .github/workflows/label.yml | 24 ------------- 5 files changed, 90 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/PR.yml create mode 100644 .github/workflows/lab2.yml delete mode 100644 .github/workflows/label.yml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 88464606..b8406164 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,7 +10,7 @@ -- [ ] A clear title (name your pr "[LAB{lab_number}] {your_student_id}") +- [ ] A clear title (name your PR "[LAB{lab_number}] {your_student_id}") - [ ] A meaningful message for PR, as well as its commits - [ ] From your specific branch (***not main or other's branch***) merging to your branch - [ ] Excluding any irrelevant files, such as binaries, text files, or dot files diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml new file mode 100644 index 00000000..598e3b99 --- /dev/null +++ b/.github/workflows/PR.yml @@ -0,0 +1,60 @@ +name: PR automation +on: + pull_request_target: + types: [opened, reopened, edited, ready_for_review] + +jobs: + labeler: + runs-on: ubuntu-latest + steps: + - name: Label PR + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.PAT }} + script: | + 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+\] [\da-zA-Z]+/; + + if (!titleRegex.test(title)) { + core.setFailed('PR title does not match the required format. Please use the format [LAB#] student#.'); + } + + if (pr.data.head.ref !== pr.data.base.ref) { + core.setFailed('The source branch and target branch must be the same.'); + } + + 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.'); + } + checklist-check: + runs-on: ubuntu-latest + steps: + - name: Check PR description + 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 body = pr.data.body; + + const checkboxes = body.match(/\- \[[x ]\]/g); + if (!checkboxes || checkboxes.length !== 5) { + core.setFailed('The PR description must contain exactly 5 checkboxes.'); + } + + const unchecked = body.match(/\- \[ \]/g); + if (unchecked && unchecked.length > 0) { + core.setFailed(`There are ${unchecked.length} unchecked items in the PR description.`); + } diff --git a/.github/workflows/lab1.yml b/.github/workflows/lab1.yml index 62e939c5..49a5d578 100644 --- a/.github/workflows/lab1.yml +++ b/.github/workflows/lab1.yml @@ -1,12 +1,12 @@ -name: lab1 +name: lab1 autograding on: pull_request: - types: [labeled] + types: [labeled, synchronize, opened, reopened, ready_for_review] jobs: build: - if: ${{ github.event.label.name == 'lab1' }} + if: contains(github.event.pull_request.labels.*.name, 'lab1') runs-on: ${{ matrix.os }} strategy: matrix: @@ -22,6 +22,5 @@ jobs: sudo apt-get install -y nodejs - name: grading run: | - echo "cd lab1" cd lab1 ./validate.sh diff --git a/.github/workflows/lab2.yml b/.github/workflows/lab2.yml new file mode 100644 index 00000000..2cb76fa7 --- /dev/null +++ b/.github/workflows/lab2.yml @@ -0,0 +1,26 @@ +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 + - name: dependency (ubuntu) + run: | + curl -fsSL https://deb.nodesource.com/setup_21.x | sudo -E bash - &&\ + sudo apt-get install -y nodejs + - name: grading + run: | + cd lab2 + ./validate.sh diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml deleted file mode 100644 index 4e7902a5..00000000 --- a/.github/workflows/label.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Automatic PR Labeler -on: - pull_request_target: - types: [opened, reopened, edited] - -jobs: - labeler: - runs-on: ubuntu-latest - steps: - - name: Label PR - uses: actions/github-script@v5 - with: - github-token: ${{ secrets.PAT }} - script: | - 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+)\]/i; - - const match = title.match(labRegex); - if (match) { - const labelToAdd = 'lab' + match[1]; - await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [labelToAdd] }); - } From 0b8e670149acb76164da1524db9ff11f4e8eabce Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 7 Mar 2024 13:53:34 +0800 Subject: [PATCH 04/10] feat: lab2 --- lab2/README.md | 19 ++++++++++++ lab2/main.js | 77 +++++++++++++++++++++++++++++++++++++++++++++++ lab2/main_test.js | 6 ++++ lab2/validate.sh | 30 ++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 lab2/README.md create mode 100644 lab2/main.js create mode 100644 lab2/main_test.js create mode 100755 lab2/validate.sh diff --git a/lab2/README.md b/lab2/README.md new file mode 100644 index 00000000..79f3870f --- /dev/null +++ b/lab2/README.md @@ -0,0 +1,19 @@ +# Lab1 + +## 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. + +## Requirement + +1. Write test cases in `main_test.js` and achieve 100% code coverage. Remember to use Mock, Spy, or Stub when necessary, you need to at least use one of them in your test cases. (100%) + +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 you modify other files to achieve requirements. + +## 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. diff --git a/lab2/main.js b/lab2/main.js new file mode 100644 index 00000000..142163d9 --- /dev/null +++ b/lab2/main.js @@ -0,0 +1,77 @@ +const fs = require('fs'); +const util = require('util'); +const readFile = util.promisify(fs.readFile); + +class MailSystem { + write(name) { + console.log('--write mail for ' + name + '--'); + const context = 'Congrats, ' + name + '!'; + return context; + } + + send(name, context) { + console.log('--send mail to ' + name + '--'); + // Interact with mail system and send mail + sleep(1000); + // random success or failure + const success = Math.random() > 0.5; + if (success) { + console.log('mail sent'); + } else { + console.log('mail failed'); + } + return success; + } +} + +class Application { + constructor() { + this.people = []; + this.selected = []; + this.mailSystem = new MailSystem(); + this.getNames().then(([people, selected]) => { + this.people = people; + this.selected = selected; + }); + } + + async getNames() { + const data = await readFile('name_list.txt', 'utf8'); + const people = data.split('\n'); + const selected = []; + return [people, selected]; + } + + getRandomPerson() { + const i = Math.floor(Math.random() * this.people.length); + return this.people[i]; + } + + selectNextPerson() { + console.log('--select next person--'); + if (this.people.length === this.selected.length) { + console.log('all selected'); + return null; + } + let person = this.getRandomPerson(); + while (this.selected.includes(person)) { + person = this.getRandomPerson(); + } + this.selected.push(person); + return person; + } + + notifySelected() { + console.log('--notify selected--'); + for (const x of this.selected) { + const context = this.mailSystem.write(x); + this.mailSystem.send(x, context); + } + } +} + +// const app = new Application(); +// app.selectNextPerson(); +// app.selectNextPerson(); +// app.selectNextPerson(); +// app.notifySelected(); \ No newline at end of file diff --git a/lab2/main_test.js b/lab2/main_test.js new file mode 100644 index 00000000..5034468e --- /dev/null +++ b/lab2/main_test.js @@ -0,0 +1,6 @@ +const 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 diff --git a/lab2/validate.sh b/lab2/validate.sh new file mode 100755 index 00000000..1f6567d4 --- /dev/null +++ b/lab2/validate.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +node=$(which node) +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab2-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 03e0b8a58198adca2d87e6bef2f3a5cf9f767fc7 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 7 Mar 2024 13:54:22 +0800 Subject: [PATCH 05/10] doc: fix typo in lab2/README --- lab2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab2/README.md b/lab2/README.md index 79f3870f..95c4afb8 100644 --- a/lab2/README.md +++ b/lab2/README.md @@ -1,4 +1,4 @@ -# Lab1 +# Lab2 ## Introduction From 76e0fb9fed560e8bf51a5b8774c08d12f8198976 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 7 Mar 2024 13:55:49 +0800 Subject: [PATCH 06/10] doc: update requirement of lab2 --- lab2/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lab2/README.md b/lab2/README.md index 95c4afb8..60a9c805 100644 --- a/lab2/README.md +++ b/lab2/README.md @@ -2,7 +2,7 @@ ## 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. +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) ## Requirement @@ -10,7 +10,10 @@ In this lab, you will write unit tests for functions implemented in `main.js`. Y 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 you modify other files to achieve 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 From d4b59207ef99c399ef675aac00d4d40dd90fa9ae Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 7 Mar 2024 14:23:43 +0800 Subject: [PATCH 07/10] feat: add validation for unwanted files in lab2 --- lab2/validate.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lab2/validate.sh b/lab2/validate.sh index 1f6567d4..13b53ed8 100755 --- a/lab2/validate.sh +++ b/lab2/validate.sh @@ -1,5 +1,13 @@ #!/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 .)" From 2d41d26ec5c615441898db8dcae6e3b2ce7000cb Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 7 Mar 2024 14:34:15 +0800 Subject: [PATCH 08/10] fix: title regex in PR labeler --- .github/workflows/PR.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index 598e3b99..10f942ec 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+\] [\da-zA-Z]+$/; if (!titleRegex.test(title)) { core.setFailed('PR title does not match the required format. Please use the format [LAB#] student#.'); From be6fb4a2aa4e6ab4e240a6f1785cbc7bb3fcc1e1 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 7 Mar 2024 18:16:46 +0800 Subject: [PATCH 09/10] fix: missing exports and use wrong undefined sleep() --- lab2/main.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lab2/main.js b/lab2/main.js index 142163d9..2e159e75 100644 --- a/lab2/main.js +++ b/lab2/main.js @@ -12,7 +12,6 @@ class MailSystem { send(name, context) { console.log('--send mail to ' + name + '--'); // Interact with mail system and send mail - sleep(1000); // random success or failure const success = Math.random() > 0.5; if (success) { @@ -74,4 +73,9 @@ class Application { // app.selectNextPerson(); // app.selectNextPerson(); // app.selectNextPerson(); -// app.notifySelected(); \ No newline at end of file +// app.notifySelected(); + +module.exports = { + Application, + MailSystem, +}; \ No newline at end of file From 95502c6910e62cd9d6cb0003ffc465d55d9a458a Mon Sep 17 00:00:00 2001 From: TaiYou-TW Date: Thu, 7 Mar 2024 22:12:35 +0800 Subject: [PATCH 10/10] fix: scripts/rebase-all.sh will fail due to too frequent remote operation --- scripts/rebase-all.sh | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/scripts/rebase-all.sh b/scripts/rebase-all.sh index 26eef16b..6e104473 100755 --- a/scripts/rebase-all.sh +++ b/scripts/rebase-all.sh @@ -12,18 +12,12 @@ for branch in $(git branch -r | grep -v HEAD); do echo "Checkout failed for branch $branch" exit 1 fi - git pull origin "$branch" - if [[ $? -ne 0 ]]; then - echo "Pull failed for branch $branch" - exit 1 - fi git rebase main if [[ $? -ne 0 ]]; then echo "Rebase failed for branch $branch" exit 1 fi - git push origin "$branch" fi done -git checkout main \ No newline at end of file +git checkout main