Skip to content

[LAB7] 511558009 #531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

---

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

- [ ] 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 all 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.)
34 changes: 19 additions & 15 deletions .github/workflows/PR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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+\] [\da-zA-Z]+/;

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);

let labNumberStr = undefined;
if (match) {
labNumberStr = match[1];
} else {
core.setFailed('PR title does not match the required format. Please use the format: [LAB#] <studentId>.');
}

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
Expand All @@ -49,12 +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 ]\]/g);
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.`);
core.setFailed(`There are ${unchecked.length} unchecked item(s) in the PR description.`);
}
56 changes: 56 additions & 0 deletions .github/workflows/lab-autograding.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Autograding

on:
pull_request_target:
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@v4
with:
ref: "${{ github.event.pull_request.merge_commit_sha }}"
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 allowedFileRegex = /^lab\d+\/main_test.js$/;
const specialChangedFiles = ["lab5/Answer.md", "lab5/antiasan.c", "lab6/Answer.md", "lab7/sol.py"];
if (!changedFiles.every((file) => (allowedFileRegex.test(file) || specialChangedFiles.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
27 changes: 0 additions & 27 deletions .github/workflows/lab1.yml

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
40 changes: 31 additions & 9 deletions lab1/main_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,43 @@ const assert = require('assert');
const { MyClass, Student } = require('./main');

test("Test MyClass's addStudent", () => {
// TODO
throw new Error("Test not implemented");
const myClass = new MyClass();
const student = new Student();
student.setName('John Doe');
const index = myClass.addStudent(student);
assert.strictEqual(index, 0); // 應該成功添加並返回索引 0

const nonStudent = {}; // 非 Student 實例
const wrongIndex = myClass.addStudent(nonStudent);
assert.strictEqual(wrongIndex, -1); // 嘗試添加非 Student 實例應返回 -1
});

test("Test MyClass's getStudentById", () => {
// TODO
throw new Error("Test not implemented");
const myClass = new MyClass();
const student = new Student();
student.setName('Jane Doe');
const index = myClass.addStudent(student);
const retrievedStudent = myClass.getStudentById(index);
assert.strictEqual(retrievedStudent, student); // 應該返回相同的學生實例

const nullStudent = myClass.getStudentById(-1); // 錯誤的索引
assert.strictEqual(nullStudent, null); // 應該返回 null
});

test("Test Student's setName", () => {
// TODO
throw new Error("Test not implemented");
const student = new Student();
student.setName('John Doe');
assert.strictEqual(student.getName(), 'John Doe'); // setName 後 getName 應該返回設置的名字

student.setName(123); // 嘗試設置非字串
assert.strictEqual(student.getName(), 'John Doe'); // 名字應該保持不變
});

test("Test Student's getName", () => {
// TODO
throw new Error("Test not implemented");
});
const student = new Student();
assert.strictEqual(student.getName(), ''); // 如果名字未設置應返回空字串

student.setName('Jane Doe');
assert.strictEqual(student.getName(), 'Jane Doe'); // setName 後 getName 應該返回設置的名字
});

22 changes: 22 additions & 0 deletions lab2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Lab2

## 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)

## 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

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.
81 changes: 81 additions & 0 deletions lab2/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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
// 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();

module.exports = {
Application,
MailSystem,
};
6 changes: 6 additions & 0 deletions lab2/main_test.js
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions lab2/validate.sh
Original file line number Diff line number Diff line change
@@ -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 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:
Loading
Loading