Skip to content

Commit dda563c

Browse files
authored
Merge branch 'SQLab:main' into main
2 parents 9e65494 + ffbfbd7 commit dda563c

24 files changed

+1675
-111
lines changed

.github/pull_request_template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77

88
---
99

10-
<!-- Please make sure you're satisfy and fill the following checkboxes -->
10+
<!-- Please make sure you're satisfied and fill in the following checkboxes -->
1111
<!-- A good PR should include the following parts: -->
1212

1313
- [ ] A clear title (name your PR "[LAB{lab_number}] {your_student_id}")
1414
- [ ] A meaningful message for PR, as well as its commits
1515
- [ ] From your specific branch (***not main or other's branch***) merging to your branch
1616
- [ ] Excluding any irrelevant files, such as binaries, text files, or dot files
17-
- [ ] Passing all CI
17+
- [ ] 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.)

.github/workflows/PR.yml

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,31 @@ jobs:
1515
const { owner, repo, number: issue_number } = context.issue;
1616
const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number });
1717
const title = pr.data.title;
18-
const labRegex = /\[LAB(\d+)\]/;
19-
const titleRegex = /\[LAB\d+\] [\da-zA-Z]+/;
2018
21-
if (!titleRegex.test(title)) {
22-
core.setFailed('PR title does not match the required format. Please use the format [LAB#] student#.');
19+
const titleRegex = /^\[LAB(\d+)\] [a-zA-Z]?\d+$/;
20+
const match = title.match(titleRegex);
21+
22+
let labNumberStr = undefined;
23+
if (match) {
24+
labNumberStr = match[1];
25+
} else {
26+
core.setFailed('PR title does not match the required format. Please use the format: [LAB#] <studentId>.');
2327
}
2428
25-
if (pr.data.head.ref !== pr.data.base.ref) {
26-
core.setFailed('The source branch and target branch must be the same.');
29+
const labelToAdd = `lab${labNumberStr}`;
30+
if (labNumberStr) {
31+
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [labelToAdd] });
2732
}
2833
2934
if (pr.data.base.ref === 'main') {
3035
core.setFailed('The target branch cannot be main.');
3136
}
3237
33-
const match = title.match(labRegex);
34-
if (match) {
35-
const labelToAdd = 'lab' + match[1];
36-
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [labelToAdd] });
37-
} else {
38-
core.setFailed('No match found in PR title. Please add a label in the format [LAB#] to the PR title.');
38+
if (labNumberStr < 3 && pr.data.head.ref !== pr.data.base.ref) {
39+
core.setFailed('The source branch and target branch must be the same.');
40+
}
41+
if (labNumberStr >= 3 && pr.data.head.ref !== labelToAdd) {
42+
core.setFailed(`The source branch must be '${labelToAdd}'`);
3943
}
4044
checklist-check:
4145
runs-on: ubuntu-latest
@@ -49,12 +53,12 @@ jobs:
4953
const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number });
5054
const body = pr.data.body;
5155
52-
const checkboxes = body.match(/\- \[[x ]\]/g);
56+
const checkboxes = body.match(/^ ?(-|\*) \[[x ]\]/gmi);
5357
if (!checkboxes || checkboxes.length !== 5) {
5458
core.setFailed('The PR description must contain exactly 5 checkboxes.');
5559
}
5660
57-
const unchecked = body.match(/\- \[ \]/g);
61+
const unchecked = body.match(/^ ?(-|\*) \[ \]/gm);
5862
if (unchecked && unchecked.length > 0) {
59-
core.setFailed(`There are ${unchecked.length} unchecked items in the PR description.`);
63+
core.setFailed(`There are ${unchecked.length} unchecked item(s) in the PR description.`);
6064
}

.github/workflows/lab-autograding.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Autograding
2+
3+
on:
4+
pull_request_target:
5+
types: [labeled, synchronize, opened, reopened, ready_for_review]
6+
7+
jobs:
8+
build:
9+
runs-on: ${{ matrix.os }}
10+
strategy:
11+
matrix:
12+
os: [ubuntu-22.04]
13+
fail-fast: false
14+
steps:
15+
- uses: actions/checkout@v4
16+
with:
17+
ref: "${{ github.event.pull_request.merge_commit_sha }}"
18+
fetch-depth: 1
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: latest
22+
- name: Extract lab number and Check no changes other than specific files
23+
uses: actions/github-script@v5
24+
id: lab
25+
with:
26+
result-encoding: string
27+
github-token: ${{ secrets.GITHUB_TOKEN }}
28+
script: |
29+
const { owner, repo, number: issue_number } = context.issue;
30+
const pr = await github.rest.pulls.get({ owner, repo, pull_number: issue_number });
31+
const labels = pr.data.labels;
32+
const lab = labels.find((label) => label.name.startsWith('lab'));
33+
if (!lab) {
34+
core.setFailed('No lab label found on the PR.');
35+
return { number: 0 };
36+
}
37+
const labNumberMatch = lab.name.match(/lab(\d+)/);
38+
if (!labNumberMatch) {
39+
core.setFailed('Invalid lab label found on the PR.');
40+
return { number: 0 };
41+
}
42+
const labNumber = labNumberMatch[1];
43+
console.log(`Lab number: ${labNumber}`)
44+
45+
const files = await github.rest.pulls.listFiles({ owner, repo, pull_number: issue_number });
46+
const changedFiles = files.data.map((file) => file.filename);
47+
const allowedFileRegex = /^lab\d+\/main_test.js$/;
48+
if (!changedFiles.every((file) => allowedFileRegex.test(file))) {
49+
core.setFailed('The PR contains changes to files other than the allowed files.');
50+
}
51+
return labNumber;
52+
- name: Grading
53+
run: |
54+
cd lab${{ steps.lab.outputs.result }}
55+
./validate.sh

.github/workflows/lab1.yml

Lines changed: 0 additions & 27 deletions
This file was deleted.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# 112-spring-software-testing-and-secure-programming
2+
23
Labs for NYCU "Software Testing and Secure Programming" course in 112 spring

lab1/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Introduction
44

5-
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.` (But remember don't commit them on GitHub)
5+
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)
66

77
## Requirement
88

lab1/validate.sh

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
1-
#!/bin/bash
2-
3-
# Check for unwanted files
4-
for file in *; do
5-
if [[ $file != "main.js" && $file != "main_test.js" && $file != "README.md" && $file != "validate.sh" ]]; then
6-
echo "[!] Unwanted file detected: $file."
7-
exit 1
8-
fi
9-
done
10-
11-
node=$(which node)
12-
test_path="${BASH_SOURCE[0]}"
13-
solution_path="$(realpath .)"
14-
tmp_dir=$(mktemp -d -t lab1-XXXXXXXXXX)
15-
16-
cd $tmp_dir
17-
18-
rm -rf *
19-
cp $solution_path/*.js .
20-
result=$($"node" --test --experimental-test-coverage) ; ret=$?
21-
if [ $ret -ne 0 ] ; then
22-
echo "[!] testing fails."
23-
exit 1
24-
else
25-
coverage=$(echo "$result" | grep 'all files' | awk -F '|' '{print $2}' | sed 's/ //g')
26-
if (( $(echo "$coverage < 100" | bc -l) )); then
27-
echo "[!] Coverage is only $coverage%, should be 100%."
28-
exit 1
29-
else
30-
echo "[V] Coverage is 100%, great job!"
31-
fi
32-
fi
33-
34-
rm -rf $tmp_dir
35-
36-
exit 0
37-
1+
#!/bin/bash
2+
3+
# Check for unwanted files
4+
for file in *; do
5+
if [[ $file != "main.js" && $file != "main_test.js" && $file != "README.md" && $file != "validate.sh" ]]; then
6+
echo "[!] Unwanted file detected: $file."
7+
exit 1
8+
fi
9+
done
10+
11+
node=$(which node)
12+
test_path="${BASH_SOURCE[0]}"
13+
solution_path="$(realpath .)"
14+
tmp_dir=$(mktemp -d -t lab1-XXXXXXXXXX)
15+
16+
cd $tmp_dir
17+
18+
rm -rf *
19+
cp $solution_path/*.js .
20+
result=$($"node" --test --experimental-test-coverage) ; ret=$?
21+
if [ $ret -ne 0 ] ; then
22+
echo "[!] testing fails."
23+
exit 1
24+
else
25+
coverage=$(echo "$result" | grep 'all files' | awk -F '|' '{print $2}' | sed 's/ //g')
26+
if (( $(echo "$coverage < 100" | bc -l) )); then
27+
echo "[!] Coverage is only $coverage%, should be 100%."
28+
exit 1
29+
else
30+
echo "[V] Coverage is 100%, great job!"
31+
fi
32+
fi
33+
34+
rm -rf $tmp_dir
35+
36+
exit 0
37+
3838
# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2:

lab2/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Lab2
2+
3+
## Introduction
4+
5+
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)
6+
7+
## Requirement
8+
9+
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%)
10+
11+
You can run `validate.sh` in your local to test if you satisfy the requirements.
12+
13+
Please note that you must not alter files other than `main_test.js`. You will get 0 points if
14+
15+
1. you modify other files to achieve requirements.
16+
2. you can't pass all CI on your PR.
17+
18+
## Submission
19+
20+
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.
21+
22+
Moreover, please submit the URL of your PR to E3. Your submission will only be accepted when you present at both places.

lab2/main.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const fs = require('fs');
2+
const util = require('util');
3+
const readFile = util.promisify(fs.readFile);
4+
5+
class MailSystem {
6+
write(name) {
7+
console.log('--write mail for ' + name + '--');
8+
const context = 'Congrats, ' + name + '!';
9+
return context;
10+
}
11+
12+
send(name, context) {
13+
console.log('--send mail to ' + name + '--');
14+
// Interact with mail system and send mail
15+
// random success or failure
16+
const success = Math.random() > 0.5;
17+
if (success) {
18+
console.log('mail sent');
19+
} else {
20+
console.log('mail failed');
21+
}
22+
return success;
23+
}
24+
}
25+
26+
class Application {
27+
constructor() {
28+
this.people = [];
29+
this.selected = [];
30+
this.mailSystem = new MailSystem();
31+
this.getNames().then(([people, selected]) => {
32+
this.people = people;
33+
this.selected = selected;
34+
});
35+
}
36+
37+
async getNames() {
38+
const data = await readFile('name_list.txt', 'utf8');
39+
const people = data.split('\n');
40+
const selected = [];
41+
return [people, selected];
42+
}
43+
44+
getRandomPerson() {
45+
const i = Math.floor(Math.random() * this.people.length);
46+
return this.people[i];
47+
}
48+
49+
selectNextPerson() {
50+
console.log('--select next person--');
51+
if (this.people.length === this.selected.length) {
52+
console.log('all selected');
53+
return null;
54+
}
55+
let person = this.getRandomPerson();
56+
while (this.selected.includes(person)) {
57+
person = this.getRandomPerson();
58+
}
59+
this.selected.push(person);
60+
return person;
61+
}
62+
63+
notifySelected() {
64+
console.log('--notify selected--');
65+
for (const x of this.selected) {
66+
const context = this.mailSystem.write(x);
67+
this.mailSystem.send(x, context);
68+
}
69+
}
70+
}
71+
72+
// const app = new Application();
73+
// app.selectNextPerson();
74+
// app.selectNextPerson();
75+
// app.selectNextPerson();
76+
// app.notifySelected();
77+
78+
module.exports = {
79+
Application,
80+
MailSystem,
81+
};

0 commit comments

Comments
 (0)