From 5738390c05cf3435095f34442e0ec9bee97394ca Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Thu, 27 Feb 2025 16:01:25 +0800 Subject: [PATCH 01/28] Update lab0.js --- lab0/lab0.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lab0/lab0.js b/lab0/lab0.js index e69de29..fa2ad5c 100644 --- a/lab0/lab0.js +++ b/lab0/lab0.js @@ -0,0 +1 @@ +console.log("Hello world!"); From 49da75467a701d114a466432a2fe0290748681bb Mon Sep 17 00:00:00 2001 From: AxelHowe Date: Wed, 5 Mar 2025 21:45:27 +0800 Subject: [PATCH 02/28] feat: lab1 --- lab1/README.md | 22 +++++++++++++++++++ lab1/main.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++ lab1/main_test.js | 23 ++++++++++++++++++++ lab1/validate.sh | 38 ++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 lab1/README.md create mode 100644 lab1/main.js create mode 100644 lab1/main_test.js create mode 100755 lab1/validate.sh diff --git a/lab1/README.md b/lab1/README.md new file mode 100644 index 0000000..df0ab9f --- /dev/null +++ b/lab1/README.md @@ -0,0 +1,22 @@ +# 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. (But remember don't commit them on GitHub) + +## Requirement + +1. Write test cases in `main_test.js` and achieve 100% code coverage. (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. diff --git a/lab1/main.js b/lab1/main.js new file mode 100644 index 0000000..c9aed9f --- /dev/null +++ b/lab1/main.js @@ -0,0 +1,55 @@ +// NOTICE: DO NOT MODIFY THE CODE IN THIS FILE +// But you can uncomment code below and run this file to understand how to use the classes + +class MyClass { + constructor() { + this.students = []; + } + + addStudent(student) { + if (!(student instanceof Student)) { + return -1; + } + this.students.push(student); + return this.students.length - 1; + } + + getStudentById(id) { + if (id < 0 || id >= this.students.length) { + return null; + } + return this.students[id]; + } +} + +class Student { + constructor() { + this.name = undefined; + } + + setName(userName) { + if (typeof userName !== 'string') { + return; + } + this.name = userName; + } + + getName() { + if (this.name === undefined) { + return ''; + } + return this.name; + } +} + +// const myClass = new MyClass(); +// const names = ['John', 'Jane', 'Doe', 'Smith']; +// names.forEach(name => { +// const student = new Student(); +// student.setName(name); +// const newStudentId = myClass.addStudent(student); +// const newStudentName = myClass.getStudentById(newStudentId).getName(); +// console.log('[+] Added student with id: %d, name: %s', newStudentId, newStudentName); +// }); + +module.exports = { MyClass, Student }; \ No newline at end of file diff --git a/lab1/main_test.js b/lab1/main_test.js new file mode 100644 index 0000000..74a716b --- /dev/null +++ b/lab1/main_test.js @@ -0,0 +1,23 @@ +const test = require('node:test'); +const assert = require('assert'); +const { MyClass, Student } = require('./main'); + +test("Test MyClass's addStudent", () => { + // TODO + throw new Error("Test not implemented"); +}); + +test("Test MyClass's getStudentById", () => { + // TODO + throw new Error("Test not implemented"); +}); + +test("Test Student's setName", () => { + // TODO + throw new Error("Test not implemented"); +}); + +test("Test Student's getName", () => { + // TODO + throw new Error("Test not implemented"); +}); \ No newline at end of file diff --git a/lab1/validate.sh b/lab1/validate.sh new file mode 100755 index 0000000..8f2fcd4 --- /dev/null +++ b/lab1/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 lab1-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%, should be 100%." + exit 1 + else + echo "[V] Coverage is 100%, great job!" + 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 9b4f698126bc67e614e02b5fab2fce94197e5d68 Mon Sep 17 00:00:00 2001 From: banana1715 Date: Sun, 9 Mar 2025 17:57:10 +0800 Subject: [PATCH 03/28] test test --- lab1/main_test.js | 54 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/lab1/main_test.js b/lab1/main_test.js index 74a716b..ac139ef 100644 --- a/lab1/main_test.js +++ b/lab1/main_test.js @@ -3,21 +3,57 @@ 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(); + + // Valid student case + const studentId = myClass.addStudent(student); + assert.strictEqual(studentId, 0); + + // Invalid input case + const invalidId = myClass.addStudent({}); + assert.strictEqual(invalidId, -1); }); test("Test MyClass's getStudentById", () => { - // TODO - throw new Error("Test not implemented"); + const myClass = new MyClass(); + const student = new Student(); + student.setName("John"); + + const studentId = myClass.addStudent(student); + + // Valid retrieval + const retrievedStudent = myClass.getStudentById(studentId); + assert.ok(retrievedStudent instanceof Student); + assert.strictEqual(retrievedStudent.getName(), "John"); + + // Invalid ID cases + assert.strictEqual(myClass.getStudentById(-1), null); + assert.strictEqual(myClass.getStudentById(100), null); }); test("Test Student's setName", () => { - // TODO - throw new Error("Test not implemented"); + const student = new Student(); + + // Valid name setting + student.setName("Jane"); + assert.strictEqual(student.getName(), "Jane"); + + // Invalid input cases + student.setName(123); + assert.strictEqual(student.getName(), "Jane"); // Should not change + + student.setName(null); + assert.strictEqual(student.getName(), "Jane"); // Should not change }); test("Test Student's getName", () => { - // TODO - throw new Error("Test not implemented"); -}); \ No newline at end of file + const student = new Student(); + + // Default value case + assert.strictEqual(student.getName(), ''); + + // After setting a valid name + student.setName("Alice"); + assert.strictEqual(student.getName(), "Alice"); +}); From f895388b602546129d591c1ab6860b42197a12ca Mon Sep 17 00:00:00 2001 From: banana1715 Date: Sun, 9 Mar 2025 18:06:01 +0800 Subject: [PATCH 04/28] test --- lab1/main_test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lab1/main_test.js b/lab1/main_test.js index ac139ef..a9c842a 100644 --- a/lab1/main_test.js +++ b/lab1/main_test.js @@ -29,9 +29,10 @@ test("Test MyClass's getStudentById", () => { // Invalid ID cases assert.strictEqual(myClass.getStudentById(-1), null); - assert.strictEqual(myClass.getStudentById(100), null); + assert.strictEqual(myClass.getStudentById(101), null); }); + test("Test Student's setName", () => { const student = new Student(); From b3f479d429ef87cd2086288b107c9389e80b2ec9 Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Sun, 9 Mar 2025 19:17:36 +0800 Subject: [PATCH 05/28] Update main_test.js --- lab1/main_test.js | 55 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/lab1/main_test.js b/lab1/main_test.js index 74a716b..a9c842a 100644 --- a/lab1/main_test.js +++ b/lab1/main_test.js @@ -3,21 +3,58 @@ 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(); + + // Valid student case + const studentId = myClass.addStudent(student); + assert.strictEqual(studentId, 0); + + // Invalid input case + const invalidId = myClass.addStudent({}); + assert.strictEqual(invalidId, -1); }); test("Test MyClass's getStudentById", () => { - // TODO - throw new Error("Test not implemented"); + const myClass = new MyClass(); + const student = new Student(); + student.setName("John"); + + const studentId = myClass.addStudent(student); + + // Valid retrieval + const retrievedStudent = myClass.getStudentById(studentId); + assert.ok(retrievedStudent instanceof Student); + assert.strictEqual(retrievedStudent.getName(), "John"); + + // Invalid ID cases + assert.strictEqual(myClass.getStudentById(-1), null); + assert.strictEqual(myClass.getStudentById(101), null); }); + test("Test Student's setName", () => { - // TODO - throw new Error("Test not implemented"); + const student = new Student(); + + // Valid name setting + student.setName("Jane"); + assert.strictEqual(student.getName(), "Jane"); + + // Invalid input cases + student.setName(123); + assert.strictEqual(student.getName(), "Jane"); // Should not change + + student.setName(null); + assert.strictEqual(student.getName(), "Jane"); // Should not change }); test("Test Student's getName", () => { - // TODO - throw new Error("Test not implemented"); -}); \ No newline at end of file + const student = new Student(); + + // Default value case + assert.strictEqual(student.getName(), ''); + + // After setting a valid name + student.setName("Alice"); + assert.strictEqual(student.getName(), "Alice"); +}); From e768b73e0b07dda059794456785c30e0123c2dc1 Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Sun, 9 Mar 2025 19:18:53 +0800 Subject: [PATCH 06/28] Update lab0.js --- lab0/lab0.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lab0/lab0.js b/lab0/lab0.js index e69de29..fa2ad5c 100644 --- a/lab0/lab0.js +++ b/lab0/lab0.js @@ -0,0 +1 @@ +console.log("Hello world!"); From ffd245b023244f9ab082512d526015994ddcb08f Mon Sep 17 00:00:00 2001 From: AxelHowe Date: Wed, 12 Mar 2025 20:52:47 +0800 Subject: [PATCH 07/28] feat: lab2 --- lab1/main_test.js | 55 ++++++-------------------------- lab2/README.md | 22 +++++++++++++ lab2/main.js | 81 +++++++++++++++++++++++++++++++++++++++++++++++ lab2/main_test.js | 6 ++++ lab2/validate.sh | 38 ++++++++++++++++++++++ 5 files changed, 156 insertions(+), 46 deletions(-) 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/lab1/main_test.js b/lab1/main_test.js index a9c842a..74a716b 100644 --- a/lab1/main_test.js +++ b/lab1/main_test.js @@ -3,58 +3,21 @@ const assert = require('assert'); const { MyClass, Student } = require('./main'); test("Test MyClass's addStudent", () => { - const myClass = new MyClass(); - const student = new Student(); - - // Valid student case - const studentId = myClass.addStudent(student); - assert.strictEqual(studentId, 0); - - // Invalid input case - const invalidId = myClass.addStudent({}); - assert.strictEqual(invalidId, -1); + // TODO + throw new Error("Test not implemented"); }); test("Test MyClass's getStudentById", () => { - const myClass = new MyClass(); - const student = new Student(); - student.setName("John"); - - const studentId = myClass.addStudent(student); - - // Valid retrieval - const retrievedStudent = myClass.getStudentById(studentId); - assert.ok(retrievedStudent instanceof Student); - assert.strictEqual(retrievedStudent.getName(), "John"); - - // Invalid ID cases - assert.strictEqual(myClass.getStudentById(-1), null); - assert.strictEqual(myClass.getStudentById(101), null); + // TODO + throw new Error("Test not implemented"); }); - test("Test Student's setName", () => { - const student = new Student(); - - // Valid name setting - student.setName("Jane"); - assert.strictEqual(student.getName(), "Jane"); - - // Invalid input cases - student.setName(123); - assert.strictEqual(student.getName(), "Jane"); // Should not change - - student.setName(null); - assert.strictEqual(student.getName(), "Jane"); // Should not change + // TODO + throw new Error("Test not implemented"); }); test("Test Student's getName", () => { - const student = new Student(); - - // Default value case - assert.strictEqual(student.getName(), ''); - - // After setting a valid name - student.setName("Alice"); - assert.strictEqual(student.getName(), "Alice"); -}); + // TODO + throw new Error("Test not implemented"); +}); \ No newline at end of file diff --git a/lab2/README.md b/lab2/README.md new file mode 100644 index 0000000..60a9c80 --- /dev/null +++ b/lab2/README.md @@ -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. diff --git a/lab2/main.js b/lab2/main.js new file mode 100644 index 0000000..2e159e7 --- /dev/null +++ b/lab2/main.js @@ -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, +}; \ No newline at end of file diff --git a/lab2/main_test.js b/lab2/main_test.js new file mode 100644 index 0000000..5034468 --- /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 0000000..13b53ed --- /dev/null +++ b/lab2/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 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 d93f35d576022e0624e6aeae01ebb18e9fea2a9a Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Wed, 19 Mar 2025 04:41:40 +0800 Subject: [PATCH 08/28] Update main_test.js --- lab2/main_test.js | 107 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/lab2/main_test.js b/lab2/main_test.js index 5034468..6b28a1d 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -1,6 +1,109 @@ const test = require('node:test'); const assert = require('assert'); +const sinon = require('sinon'); +const fs = require('fs').promises; 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 +test('MailSystem - write should return correct message', () => { + const mailSystem = new MailSystem(); + const spy = sinon.spy(mailSystem, 'write'); + + const name = 'Alice'; + const expectedMessage = `Congrats, ${name}!`; + const result = mailSystem.write(name); + + assert.strictEqual(result, expectedMessage); + assert.strictEqual(spy.calledOnce, true); + assert.strictEqual(spy.calledWith(name), true); + + spy.restore(); +}); + +test('MailSystem - send should return boolean', () => { + const mailSystem = new MailSystem(); + const stub = sinon.stub(mailSystem, 'send').returns(true); // Force success + + const success = mailSystem.send('Alice', 'Test Message'); + + assert.strictEqual(success, true); + assert.strictEqual(stub.calledOnce, true); + + stub.restore(); +}); + +test('MailSystem - sendWithRetry should retry on failure', async () => { + const mailSystem = new MailSystem(); + const sendStub = sinon.stub(mailSystem, 'send').returns(false); // Always fail + + const success = await mailSystem.sendWithRetry('Alice', 'Test Message', 3); + + assert.strictEqual(success, false); + assert.strictEqual(sendStub.callCount, 3); // Should retry 3 times + + sendStub.restore(); +}); + +test('Application - getNames should read and split names correctly', async () => { + const fakeFileData = 'Alice\nBob\nCharlie'; + const readStub = sinon.stub(fs, 'readFile').resolves(fakeFileData); + + const app = new Application(); + await app.init(); + + assert.deepStrictEqual(app.people, ['Alice', 'Bob', 'Charlie']); + assert.deepStrictEqual(app.selected, []); + + assert.strictEqual(readStub.calledOnce, true); + readStub.restore(); +}); + +test('Application - getNames should handle empty file gracefully', async () => { + const readStub = sinon.stub(fs, 'readFile').resolves(''); + + const app = new Application(); + await app.init(); + + assert.deepStrictEqual(app.people, []); + assert.deepStrictEqual(app.selected, []); + + assert.strictEqual(readStub.calledOnce, true); + readStub.restore(); +}); + +test('Application - selectNextPerson should not select the same person twice', async () => { + const app = new Application(); + app.people = ['Alice', 'Bob', 'Charlie']; + + const spy = sinon.spy(app, 'selectNextPerson'); + + const selected = new Set(); + for (let i = 0; i < app.people.length; i++) { + const person = app.selectNextPerson(); + assert.strictEqual(selected.has(person), false); + selected.add(person); + } + + assert.strictEqual(selected.size, 3); + assert.strictEqual(app.selectNextPerson(), null); + assert.strictEqual(spy.callCount, 4); // Called one extra time when all are selected + + spy.restore(); +}); + +test('Application - notifySelected should call MailSystem correctly', async () => { + const app = new Application(); + app.mailSystem = new MailSystem(); + + const writeSpy = sinon.spy(app.mailSystem, 'write'); + const sendMock = sinon.mock(app.mailSystem); + sendMock.expects('sendWithRetry').twice().resolves(true); + + app.selected = ['Alice', 'Bob']; + await app.notifySelected(); + + assert.strictEqual(writeSpy.calledTwice, true); + sendMock.verify(); + + writeSpy.restore(); + sendMock.restore(); +}); From 9495b8809868c10963cff22617069947a521a78d Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Wed, 19 Mar 2025 14:27:32 +0800 Subject: [PATCH 09/28] Update main_test.js --- lab2/main_test.js | 201 +++++++++++++++++++++++++++------------------- 1 file changed, 119 insertions(+), 82 deletions(-) diff --git a/lab2/main_test.js b/lab2/main_test.js index 6b28a1d..55e2d3f 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -1,109 +1,146 @@ const test = require('node:test'); const assert = require('assert'); -const sinon = require('sinon'); -const fs = require('fs').promises; +const fs = require('fs'); const { Application, MailSystem } = require('./main'); -test('MailSystem - write should return correct message', () => { - const mailSystem = new MailSystem(); - const spy = sinon.spy(mailSystem, 'write'); +// Utility function to delay a bit +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} - const name = 'Alice'; - const expectedMessage = `Congrats, ${name}!`; - const result = mailSystem.write(name); +// Test Application.getNames by writing a temporary file 'name_list.txt' +test('Application.getNames should read names from file', async () => { + const testData = "Alice\nBob\nCharlie"; + fs.writeFileSync('name_list.txt', testData, 'utf8'); - assert.strictEqual(result, expectedMessage); - assert.strictEqual(spy.calledOnce, true); - assert.strictEqual(spy.calledWith(name), true); + const app = new Application(); + const [people, selected] = await app.getNames(); + assert.deepStrictEqual(people, ['Alice', 'Bob', 'Charlie']); - spy.restore(); + // Clean up the temporary file + fs.unlinkSync('name_list.txt'); }); -test('MailSystem - send should return boolean', () => { - const mailSystem = new MailSystem(); - const stub = sinon.stub(mailSystem, 'send').returns(true); // Force success +// Test that the Application constructor initializes people and selected correctly. +test('Application constructor should initialize people and selected', async () => { + const testData = "Alice\nBob"; + fs.writeFileSync('name_list.txt', testData, 'utf8'); - const success = mailSystem.send('Alice', 'Test Message'); + const app = new Application(); + // Wait a bit for the asynchronous constructor call to complete. + await delay(20); + assert.deepStrictEqual(app.people, ['Alice', 'Bob']); + assert.deepStrictEqual(app.selected, []); - assert.strictEqual(success, true); - assert.strictEqual(stub.calledOnce, true); - - stub.restore(); + fs.unlinkSync('name_list.txt'); }); -test('MailSystem - sendWithRetry should retry on failure', async () => { - const mailSystem = new MailSystem(); - const sendStub = sinon.stub(mailSystem, 'send').returns(false); // Always fail - - const success = await mailSystem.sendWithRetry('Alice', 'Test Message', 3); +// Test Application.getRandomPerson returns a valid name. +test('Application.getRandomPerson should return a valid name', () => { + const app = new Application(); + app.people = ['Alice', 'Bob', 'Charlie']; - assert.strictEqual(success, false); - assert.strictEqual(sendStub.callCount, 3); // Should retry 3 times + const originalRandom = Math.random; + Math.random = () => 0.5; // For 3 items, floor(0.5 * 3) = 1, expecting 'Bob' + + const person = app.getRandomPerson(); + assert.strictEqual(person, 'Bob'); - sendStub.restore(); + Math.random = originalRandom; }); -test('Application - getNames should read and split names correctly', async () => { - const fakeFileData = 'Alice\nBob\nCharlie'; - const readStub = sinon.stub(fs, 'readFile').resolves(fakeFileData); - - const app = new Application(); - await app.init(); - - assert.deepStrictEqual(app.people, ['Alice', 'Bob', 'Charlie']); - assert.deepStrictEqual(app.selected, []); - - assert.strictEqual(readStub.calledOnce, true); - readStub.restore(); +// Test Application.selectNextPerson avoids duplicate selections. +test('Application.selectNextPerson should avoid duplicates', () => { + const app = new Application(); + app.people = ['Alice', 'Bob', 'Charlie']; + app.selected = ['Alice']; + + const originalRandom = Math.random; + // Force Math.random to return a value that selects 'Charlie' + Math.random = () => 0.8; // floor(0.8 * 3) = 2 -> 'Charlie' + + const person = app.selectNextPerson(); + assert.strictEqual(person, 'Charlie'); + assert.strictEqual(app.selected.length, 2); + + Math.random = originalRandom; }); -test('Application - getNames should handle empty file gracefully', async () => { - const readStub = sinon.stub(fs, 'readFile').resolves(''); - - const app = new Application(); - await app.init(); +// Test Application.selectNextPerson returns null if all persons have been selected. +test('Application.selectNextPerson should return null if all selected', () => { + const app = new Application(); + app.people = ['Alice', 'Bob']; + app.selected = ['Alice', 'Bob']; - assert.deepStrictEqual(app.people, []); - assert.deepStrictEqual(app.selected, []); - - assert.strictEqual(readStub.calledOnce, true); - readStub.restore(); + const person = app.selectNextPerson(); + assert.strictEqual(person, null); }); -test('Application - selectNextPerson should not select the same person twice', async () => { - const app = new Application(); - app.people = ['Alice', 'Bob', 'Charlie']; - - const spy = sinon.spy(app, 'selectNextPerson'); - - const selected = new Set(); - for (let i = 0; i < app.people.length; i++) { - const person = app.selectNextPerson(); - assert.strictEqual(selected.has(person), false); - selected.add(person); - } - - assert.strictEqual(selected.size, 3); - assert.strictEqual(app.selectNextPerson(), null); - assert.strictEqual(spy.callCount, 4); // Called one extra time when all are selected - - spy.restore(); +// Test MailSystem.write generates the correct mail content. +test('MailSystem.write should generate correct mail content', () => { + const mailSystem = new MailSystem(); + const content = mailSystem.write('Alice'); + assert.strictEqual(content, 'Congrats, Alice!'); }); -test('Application - notifySelected should call MailSystem correctly', async () => { - const app = new Application(); - app.mailSystem = new MailSystem(); - - const writeSpy = sinon.spy(app.mailSystem, 'write'); - const sendMock = sinon.mock(app.mailSystem); - sendMock.expects('sendWithRetry').twice().resolves(true); - - app.selected = ['Alice', 'Bob']; - await app.notifySelected(); +// Test MailSystem.send returns true when Math.random is high. +test('MailSystem.send should return true when Math.random is high', () => { + const mailSystem = new MailSystem(); + const originalRandom = Math.random; + Math.random = () => 0.9; // Simulate success + + const result = mailSystem.send('Alice', 'Congrats, Alice!'); + assert.strictEqual(result, true); + + Math.random = originalRandom; +}); - assert.strictEqual(writeSpy.calledTwice, true); - sendMock.verify(); +// Test MailSystem.send returns false when Math.random is low. +test('MailSystem.send should return false when Math.random is low', () => { + const mailSystem = new MailSystem(); + const originalRandom = Math.random; + Math.random = () => 0.1; // Simulate failure + + const result = mailSystem.send('Alice', 'Congrats, Alice!'); + assert.strictEqual(result, false); + + Math.random = originalRandom; +}); - writeSpy.restore(); - sendMock.restore(); +// Test Application.notifySelected calls write and send for each person. +test('Application.notifySelected should call write and send for each person', () => { + const app = new Application(); + app.selected = ['Alice', 'Bob']; + + // Create arrays to record calls. + let writeCalls = []; + let sendCalls = []; + + // Backup original methods. + const originalWrite = app.mailSystem.write; + const originalSend = app.mailSystem.send; + + // Override write and send. + app.mailSystem.write = function(name) { + writeCalls.push(name); + return 'Congrats!'; + }; + + app.mailSystem.send = function(name, context) { + sendCalls.push({ name, context }); + return true; + }; + + app.notifySelected(); + + // Verify that write was called for each selected person. + assert.deepStrictEqual(writeCalls, ['Alice', 'Bob']); + // Verify that send was called with the correct parameters. + assert.strictEqual(sendCalls.length, 2); + assert.deepStrictEqual(sendCalls[0], { name: 'Alice', context: 'Congrats!' }); + assert.deepStrictEqual(sendCalls[1], { name: 'Bob', context: 'Congrats!' }); + + // Restore original methods. + app.mailSystem.write = originalWrite; + app.mailSystem.send = originalSend; }); From 96dc9b0c7e262f0455561eba657141584b651937 Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Wed, 19 Mar 2025 14:33:26 +0800 Subject: [PATCH 10/28] Update main_test.js --- lab2/main_test.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lab2/main_test.js b/lab2/main_test.js index 55e2d3f..fbcbbbe 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -144,3 +144,26 @@ test('Application.notifySelected should call write and send for each person', () app.mailSystem.write = originalWrite; app.mailSystem.send = originalSend; }); +// Test that selectNextPerson loops until a non-duplicate is returned. +test('Application.selectNextPerson should loop until a non-duplicate is selected', () => { + const app = new Application(); + app.people = ['Alice', 'Bob', 'Charlie']; + app.selected = ['Alice']; // Already selected 'Alice' + + // Override getRandomPerson to simulate a duplicate on first call, + // and a unique name on the second call. + let callCount = 0; + app.getRandomPerson = function() { + callCount++; + if (callCount === 1) { + return 'Alice'; // Duplicate value + } else { + return 'Bob'; // New value, not in selected + } + }; + + const selectedPerson = app.selectNextPerson(); + assert.strictEqual(selectedPerson, 'Bob'); + assert.strictEqual(callCount, 2); // Should call getRandomPerson twice + + }); From 3b5c218b050cf94fc54df04df8e066106c96631e Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:02:09 +0800 Subject: [PATCH 11/28] Update main_test.js --- lab2/main_test.js | 102 +++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/lab2/main_test.js b/lab2/main_test.js index fbcbbbe..4bceee3 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -3,31 +3,44 @@ const assert = require('assert'); const fs = require('fs'); const { Application, MailSystem } = require('./main'); -// Utility function to delay a bit +// Helper: create Application without triggering file read error. +// Override getNames so that it immediately returns a resolved promise. +function createTestApplicationWithoutFileRead() { + const originalGetNames = Application.prototype.getNames; + Application.prototype.getNames = function() { + return Promise.resolve([this.people, this.selected]); + }; + const app = new Application(); + // Immediately restore the original getNames for any later use. + Application.prototype.getNames = originalGetNames; + return app; +} + +// Utility function for a small delay. function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -// Test Application.getNames by writing a temporary file 'name_list.txt' +// Test: Application.getNames should read names from file. test('Application.getNames should read names from file', async () => { const testData = "Alice\nBob\nCharlie"; fs.writeFileSync('name_list.txt', testData, 'utf8'); + // Use the real getNames. const app = new Application(); const [people, selected] = await app.getNames(); assert.deepStrictEqual(people, ['Alice', 'Bob', 'Charlie']); - // Clean up the temporary file fs.unlinkSync('name_list.txt'); }); -// Test that the Application constructor initializes people and selected correctly. +// Test: Application constructor should initialize people and selected. test('Application constructor should initialize people and selected', async () => { const testData = "Alice\nBob"; fs.writeFileSync('name_list.txt', testData, 'utf8'); const app = new Application(); - // Wait a bit for the asynchronous constructor call to complete. + // Wait a bit for the asynchronous constructor to complete. await delay(20); assert.deepStrictEqual(app.people, ['Alice', 'Bob']); assert.deepStrictEqual(app.selected, []); @@ -35,13 +48,13 @@ test('Application constructor should initialize people and selected', async () = fs.unlinkSync('name_list.txt'); }); -// Test Application.getRandomPerson returns a valid name. +// Test: getRandomPerson returns a valid name. test('Application.getRandomPerson should return a valid name', () => { - const app = new Application(); + const app =tionWithoutFileRead(); app.people = ['Alice', 'Bob', 'Charlie']; const originalRandom = Math.random; - Math.random = () => 0.5; // For 3 items, floor(0.5 * 3) = 1, expecting 'Bob' + Math.random = () => 0.5; // floor(0.5 * 3) = 1, expecting 'Bob' const person = app.getRandomPerson(); assert.strictEqual(person, 'Bob'); @@ -49,14 +62,14 @@ test('Application.getRandomPerson should return a valid name', () => { Math.random = originalRandom; }); -// Test Application.selectNextPerson avoids duplicate selections. +// Test: selectNextPerson avoids duplicate selections. test('Application.selectNextPerson should avoid duplicates', () => { - const app = new Application(); + const app = createTestApplicationWithoutFileRead(); app.people = ['Alice', 'Bob', 'Charlie']; app.selected = ['Alice']; const originalRandom = Math.random; - // Force Math.random to return a value that selects 'Charlie' + // Force Math.random to return a value that se createTestApplicalects 'Charlie' Math.random = () => 0.8; // floor(0.8 * 3) = 2 -> 'Charlie' const person = app.selectNextPerson(); @@ -66,9 +79,9 @@ test('Application.selectNextPerson should avoid duplicates', () => { Math.random = originalRandom; }); -// Test Application.selectNextPerson returns null if all persons have been selected. +// Test: selectNextPerson returns null if all persons have been selected. test('Application.selectNextPerson should return null if all selected', () => { - const app = new Application(); + const app = createTestApplicationWithoutFileRead(); app.people = ['Alice', 'Bob']; app.selected = ['Alice', 'Bob']; @@ -76,14 +89,37 @@ test('Application.selectNextPerson should return null if all selected', () => { assert.strictEqual(person, null); }); -// Test MailSystem.write generates the correct mail content. +// Test: selectNextPerson loops until a non-duplicate is selected. +test('Application.selectNextPerson should loop until a non-duplicate is selected', () => { + const app = createTestApplicationWithoutFileRead(); + app.people = ['Alice', 'Bob', 'Charlie']; + app.selected = ['Alice']; // 'Alice' already selected + + // Override getRandomPerson to simulate a duplicate on first call, + // then a unique name on the second call. + let callCount = 0; + app.getRandomPerson = function() { + callCount++; + if (callCount === 1) { + return 'Alice'; // duplicate value + } else { + return 'Bob'; // new value + } + }; + + const selectedPerson = app.selectNextPerson(); + assert.strictEqual(selectedPerson, 'Bob'); + assert.strictEqual(callCount, 2); // getRandomPerson should be called twice +}); + +// Test: MailSystem.write generates the correct mail content. test('MailSystem.write should generate correct mail content', () => { const mailSystem = new MailSystem(); const content = mailSystem.write('Alice'); assert.strictEqual(content, 'Congrats, Alice!'); }); -// Test MailSystem.send returns true when Math.random is high. +// Test: MailSystem.send returns true when Math.random is high. test('MailSystem.send should return true when Math.random is high', () => { const mailSystem = new MailSystem(); const originalRandom = Math.random; @@ -95,7 +131,7 @@ test('MailSystem.send should return true when Math.random is high', () => { Math.random = originalRandom; }); -// Test MailSystem.send returns false when Math.random is low. +// Test: MailSystem.send returns false when Math.random is low. test('MailSystem.send should return false when Math.random is low', () => { const mailSystem = new MailSystem(); const originalRandom = Math.random; @@ -107,20 +143,18 @@ test('MailSystem.send should return false when Math.random is low', () => { Math.random = originalRandom; }); -// Test Application.notifySelected calls write and send for each person. +// Test: notifySelected calls write and send for each selected person. test('Application.notifySelected should call write and send for each person', () => { - const app = new Application(); + const app = createTestApplicationWithoutFileRead(); app.selected = ['Alice', 'Bob']; - // Create arrays to record calls. let writeCalls = []; let sendCalls = []; - // Backup original methods. const originalWrite = app.mailSystem.write; const originalSend = app.mailSystem.send; - // Override write and send. + // Override write and send to record calls. app.mailSystem.write = function(name) { writeCalls.push(name); return 'Congrats!'; @@ -133,37 +167,11 @@ test('Application.notifySelected should call write and send for each person', () app.notifySelected(); - // Verify that write was called for each selected person. assert.deepStrictEqual(writeCalls, ['Alice', 'Bob']); - // Verify that send was called with the correct parameters. assert.strictEqual(sendCalls.length, 2); assert.deepStrictEqual(sendCalls[0], { name: 'Alice', context: 'Congrats!' }); assert.deepStrictEqual(sendCalls[1], { name: 'Bob', context: 'Congrats!' }); - // Restore original methods. app.mailSystem.write = originalWrite; app.mailSystem.send = originalSend; }); -// Test that selectNextPerson loops until a non-duplicate is returned. -test('Application.selectNextPerson should loop until a non-duplicate is selected', () => { - const app = new Application(); - app.people = ['Alice', 'Bob', 'Charlie']; - app.selected = ['Alice']; // Already selected 'Alice' - - // Override getRandomPerson to simulate a duplicate on first call, - // and a unique name on the second call. - let callCount = 0; - app.getRandomPerson = function() { - callCount++; - if (callCount === 1) { - return 'Alice'; // Duplicate value - } else { - return 'Bob'; // New value, not in selected - } - }; - - const selectedPerson = app.selectNextPerson(); - assert.strictEqual(selectedPerson, 'Bob'); - assert.strictEqual(callCount, 2); // Should call getRandomPerson twice - - }); From 72ded48751df32d255290098c433774caa4a31ca Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:04:21 +0800 Subject: [PATCH 12/28] Update main_test.js --- lab2/main_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lab2/main_test.js b/lab2/main_test.js index 4bceee3..8721535 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -50,7 +50,7 @@ test('Application constructor should initialize people and selected', async () = // Test: getRandomPerson returns a valid name. test('Application.getRandomPerson should return a valid name', () => { - const app =tionWithoutFileRead(); + const app = createTestApplicationWithoutFileRead(); app.people = ['Alice', 'Bob', 'Charlie']; const originalRandom = Math.random; @@ -69,7 +69,7 @@ test('Application.selectNextPerson should avoid duplicates', () => { app.selected = ['Alice']; const originalRandom = Math.random; - // Force Math.random to return a value that se createTestApplicalects 'Charlie' + // Force Math.random to return a value that selects 'Charlie' Math.random = () => 0.8; // floor(0.8 * 3) = 2 -> 'Charlie' const person = app.selectNextPerson(); From f91e0f3b2407d1819f0dd5df5a84d02e67c6e365 Mon Sep 17 00:00:00 2001 From: CTHua Date: Thu, 20 Mar 2025 15:35:06 +0800 Subject: [PATCH 13/28] feat: lab3 --- lab2/main_test.js | 175 +--------------------------------------------- lab3/README.md | 29 ++++++++ lab3/main.js | 34 +++++++++ lab3/main_test.js | 5 ++ lab3/validate.sh | 38 ++++++++++ 5 files changed, 108 insertions(+), 173 deletions(-) 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/lab2/main_test.js b/lab2/main_test.js index 8721535..5034468 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -1,177 +1,6 @@ const test = require('node:test'); const assert = require('assert'); -const fs = require('fs'); const { Application, MailSystem } = require('./main'); -// Helper: create Application without triggering file read error. -// Override getNames so that it immediately returns a resolved promise. -function createTestApplicationWithoutFileRead() { - const originalGetNames = Application.prototype.getNames; - Application.prototype.getNames = function() { - return Promise.resolve([this.people, this.selected]); - }; - const app = new Application(); - // Immediately restore the original getNames for any later use. - Application.prototype.getNames = originalGetNames; - return app; -} - -// Utility function for a small delay. -function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -// Test: Application.getNames should read names from file. -test('Application.getNames should read names from file', async () => { - const testData = "Alice\nBob\nCharlie"; - fs.writeFileSync('name_list.txt', testData, 'utf8'); - - // Use the real getNames. - const app = new Application(); - const [people, selected] = await app.getNames(); - assert.deepStrictEqual(people, ['Alice', 'Bob', 'Charlie']); - - fs.unlinkSync('name_list.txt'); -}); - -// Test: Application constructor should initialize people and selected. -test('Application constructor should initialize people and selected', async () => { - const testData = "Alice\nBob"; - fs.writeFileSync('name_list.txt', testData, 'utf8'); - - const app = new Application(); - // Wait a bit for the asynchronous constructor to complete. - await delay(20); - assert.deepStrictEqual(app.people, ['Alice', 'Bob']); - assert.deepStrictEqual(app.selected, []); - - fs.unlinkSync('name_list.txt'); -}); - -// Test: getRandomPerson returns a valid name. -test('Application.getRandomPerson should return a valid name', () => { - const app = createTestApplicationWithoutFileRead(); - app.people = ['Alice', 'Bob', 'Charlie']; - - const originalRandom = Math.random; - Math.random = () => 0.5; // floor(0.5 * 3) = 1, expecting 'Bob' - - const person = app.getRandomPerson(); - assert.strictEqual(person, 'Bob'); - - Math.random = originalRandom; -}); - -// Test: selectNextPerson avoids duplicate selections. -test('Application.selectNextPerson should avoid duplicates', () => { - const app = createTestApplicationWithoutFileRead(); - app.people = ['Alice', 'Bob', 'Charlie']; - app.selected = ['Alice']; - - const originalRandom = Math.random; - // Force Math.random to return a value that selects 'Charlie' - Math.random = () => 0.8; // floor(0.8 * 3) = 2 -> 'Charlie' - - const person = app.selectNextPerson(); - assert.strictEqual(person, 'Charlie'); - assert.strictEqual(app.selected.length, 2); - - Math.random = originalRandom; -}); - -// Test: selectNextPerson returns null if all persons have been selected. -test('Application.selectNextPerson should return null if all selected', () => { - const app = createTestApplicationWithoutFileRead(); - app.people = ['Alice', 'Bob']; - app.selected = ['Alice', 'Bob']; - - const person = app.selectNextPerson(); - assert.strictEqual(person, null); -}); - -// Test: selectNextPerson loops until a non-duplicate is selected. -test('Application.selectNextPerson should loop until a non-duplicate is selected', () => { - const app = createTestApplicationWithoutFileRead(); - app.people = ['Alice', 'Bob', 'Charlie']; - app.selected = ['Alice']; // 'Alice' already selected - - // Override getRandomPerson to simulate a duplicate on first call, - // then a unique name on the second call. - let callCount = 0; - app.getRandomPerson = function() { - callCount++; - if (callCount === 1) { - return 'Alice'; // duplicate value - } else { - return 'Bob'; // new value - } - }; - - const selectedPerson = app.selectNextPerson(); - assert.strictEqual(selectedPerson, 'Bob'); - assert.strictEqual(callCount, 2); // getRandomPerson should be called twice -}); - -// Test: MailSystem.write generates the correct mail content. -test('MailSystem.write should generate correct mail content', () => { - const mailSystem = new MailSystem(); - const content = mailSystem.write('Alice'); - assert.strictEqual(content, 'Congrats, Alice!'); -}); - -// Test: MailSystem.send returns true when Math.random is high. -test('MailSystem.send should return true when Math.random is high', () => { - const mailSystem = new MailSystem(); - const originalRandom = Math.random; - Math.random = () => 0.9; // Simulate success - - const result = mailSystem.send('Alice', 'Congrats, Alice!'); - assert.strictEqual(result, true); - - Math.random = originalRandom; -}); - -// Test: MailSystem.send returns false when Math.random is low. -test('MailSystem.send should return false when Math.random is low', () => { - const mailSystem = new MailSystem(); - const originalRandom = Math.random; - Math.random = () => 0.1; // Simulate failure - - const result = mailSystem.send('Alice', 'Congrats, Alice!'); - assert.strictEqual(result, false); - - Math.random = originalRandom; -}); - -// Test: notifySelected calls write and send for each selected person. -test('Application.notifySelected should call write and send for each person', () => { - const app = createTestApplicationWithoutFileRead(); - app.selected = ['Alice', 'Bob']; - - let writeCalls = []; - let sendCalls = []; - - const originalWrite = app.mailSystem.write; - const originalSend = app.mailSystem.send; - - // Override write and send to record calls. - app.mailSystem.write = function(name) { - writeCalls.push(name); - return 'Congrats!'; - }; - - app.mailSystem.send = function(name, context) { - sendCalls.push({ name, context }); - return true; - }; - - app.notifySelected(); - - assert.deepStrictEqual(writeCalls, ['Alice', 'Bob']); - assert.strictEqual(sendCalls.length, 2); - assert.deepStrictEqual(sendCalls[0], { name: 'Alice', context: 'Congrats!' }); - assert.deepStrictEqual(sendCalls[1], { name: 'Bob', context: 'Congrats!' }); - - app.mailSystem.write = originalWrite; - app.mailSystem.send = originalSend; -}); +// TODO: write your tests here +// Remember to use Stub, Mock, and Spy when necessary \ No newline at end of file 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..e6d6414 --- /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 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 From 646d772c08fba7c740f302ea19db81cf091aa7db Mon Sep 17 00:00:00 2001 From: CTHua Date: Thu, 27 Mar 2025 13:32:19 +0800 Subject: [PATCH 14/28] feat: lab4 --- lab4/README.md | 29 + lab4/main_test.js | 22 + lab4/package-lock.json | 1174 ++++++++++++++++++++++++++++++++++++++++ lab4/package.json | 5 + lab4/validate.sh | 45 ++ 5 files changed, 1275 insertions(+) create mode 100644 lab4/README.md create mode 100644 lab4/main_test.js create mode 100644 lab4/package-lock.json create mode 100644 lab4/package.json create mode 100755 lab4/validate.sh diff --git a/lab4/README.md b/lab4/README.md new file mode 100644 index 0000000..c9cb0b7 --- /dev/null +++ b/lab4/README.md @@ -0,0 +1,29 @@ +# Lab4 + +## Introduction + +In this lab, you will write tests in `main_test.js`. You can learn how to use [Puppeteer](https://pptr.dev/) to tests a web UI. + +## Preparation (Important!!!) + +1. Sync fork your branch (e.g., `SQLab:312XXXXXX`) +2. `git checkout -b lab4` (**NOT** your student ID !!!) + +## Requirement + +1. (100%) Goto https://pptr.dev/, type `chipi chipi chapa chapa` into the search box, click on **1st** result in the **Docs** section, and print the title. + +For the detailed steps and hints, please check the slide of this lab. + +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/lab4/main_test.js b/lab4/main_test.js new file mode 100644 index 0000000..e37d21a --- /dev/null +++ b/lab4/main_test.js @@ -0,0 +1,22 @@ +const puppeteer = require('puppeteer'); + +(async () => { + // Launch the browser and open a new blank page + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + + // Navigate the page to a URL + await page.goto('https://pptr.dev/'); + + // Hints: + // Click search button + // Type into search box + // Wait for search result + // Get the `Docs` result section + // Click on first result in `Docs` section + // Locate the title + // Print the title + + // Close the browser + await browser.close(); +})(); \ No newline at end of file diff --git a/lab4/package-lock.json b/lab4/package-lock.json new file mode 100644 index 0000000..ced9fee --- /dev/null +++ b/lab4/package-lock.json @@ -0,0 +1,1174 @@ +{ + "name": "lab4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "puppeteer": "^22.5.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.1.tgz", + "integrity": "sha512-bC49z4spJQR3j8vFtJBLqzyzFV0ciuL5HYX7qfSl3KEqeMVV+eTquRvmXxpvB0AMubRrvv7y5DILiLLPi57Ewg==", + "dependencies": { + "@babel/highlight": "^7.24.1", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.1.tgz", + "integrity": "sha512-EPmDPxidWe/Ex+HTFINpvXdPHRmgSF3T8hGvzondYjmgzTQ/0EbLpSxyt+w3zzlYSk9cNBQNF9k0dT5Z2NiBjw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.0.tgz", + "integrity": "sha512-MC7LxpcBtdfTbzwARXIkqGZ1Osn3nnZJlm+i0+VqHl72t//Xwl9wICrXT8BwtgC6s1xJNHsxOpvzISUqe92+sw==", + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.4.0", + "semver": "7.6.0", + "tar-fs": "3.0.5", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, + "node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "optional": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + }, + "node_modules/bare-events": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.1.tgz", + "integrity": "sha512-9GYPpsPFvrWBkelIhOhTWtkeZxVxZOdb3VnFTCzlOo3OjvmTvzLoZFUT8kNFACx0vJej6QPney1Cf9BvzCNE/A==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.2.2.tgz", + "integrity": "sha512-X9IqgvyB0/VA5OZJyb5ZstoN62AzD7YxVGog13kkfYWYqJYcK0kcqLZ6TrmH5qr4/8//ejVcX4x/a0UvaogXmA==", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-os": "^2.0.0", + "bare-path": "^2.0.0", + "streamx": "^2.13.0" + } + }, + "node_modules/bare-os": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.2.1.tgz", + "integrity": "sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w==", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.0.tgz", + "integrity": "sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw==", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chromium-bidi": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.13.tgz", + "integrity": "sha512-OHbYCetDxdW/xmlrafgOiLsIrw4Sp1BEeolbZ1UGJO5v/nekQOJBj/Kzyw6sqKcAVabUTo0GS3cTYgr6zIf00g==", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.22.4" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "22.5.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.5.0.tgz", + "integrity": "sha512-PNVflixb6w3FMhehYhLcaQHTCcNKVkjxekzyvWr0n0yBnhUYF0ZhiG4J1I14Mzui2oW8dGvUD8kbXj0GiN1pFg==", + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.2.0", + "cosmiconfig": "9.0.0", + "puppeteer-core": "22.5.0" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "22.5.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.5.0.tgz", + "integrity": "sha512-bcfmM1nNSysjnES/ZZ1KdwFAFFGL3N76qRpisBb4WL7f4UAD4vPDxlhKZ1HJCDgMSWeYmeder4kftyp6lKqMYg==", + "dependencies": { + "@puppeteer/browsers": "2.2.0", + "chromium-bidi": "0.5.13", + "debug": "4.3.4", + "devtools-protocol": "0.0.1249869", + "ws": "8.16.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/streamx": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", + "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar-fs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "optional": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/lab4/package.json b/lab4/package.json new file mode 100644 index 0000000..5014ba0 --- /dev/null +++ b/lab4/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "puppeteer": "^22.5.0" + } +} diff --git a/lab4/validate.sh b/lab4/validate.sh new file mode 100755 index 0000000..1130e5c --- /dev/null +++ b/lab4/validate.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "node_modules" && $file != "main_test.js" && $file != "package-lock.json" && $file != "package.json" && $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 lab4-XXXXXXXXXX) +answer="ElementHandle.dragAndDrop() method" + +cd $tmp_dir + +rm -rf * +cp $solution_path/*.js . +cp $solution_path/*.json . + +# Install dependencies +npm ci + +result=$($"node" main_test.js) ; ret=$? +if [ $ret -ne 0 ] ; then + echo "[!] testing fails" + exit 1 +else + title=$(echo "$result" | head -1) + if [[ $title != $answer ]]; then + echo "[!] Expected: $answer" + echo "[!] Actual: $title" + exit 1 + else + echo "[V] Pass" + 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 a6492bd4887790bc527a8a08cc7c31ecda52849c Mon Sep 17 00:00:00 2001 From: YingMuo Date: Thu, 24 Apr 2025 06:03:24 +0800 Subject: [PATCH 15/28] feat: lab5 --- lab5/Makefile | 19 +++++++++++++++++++ lab5/README.md | 26 ++++++++++++++++++++++++++ lab5/ans | 4 ++++ lab5/antiasan.c | 6 ++++++ lab5/antiasan.h | 6 ++++++ lab5/bss_overflow.c | 21 +++++++++++++++++++++ lab5/validate.sh | 43 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 125 insertions(+) create mode 100644 lab5/Makefile create mode 100644 lab5/README.md create mode 100644 lab5/ans create mode 100644 lab5/antiasan.c create mode 100644 lab5/antiasan.h create mode 100644 lab5/bss_overflow.c create mode 100755 lab5/validate.sh diff --git a/lab5/Makefile b/lab5/Makefile new file mode 100644 index 0000000..266dba9 --- /dev/null +++ b/lab5/Makefile @@ -0,0 +1,19 @@ +CC=gcc + +.PHONY: all +all: bss_overflow_asan + +bss_overflow_asan: bss_overflow.c libantiasan.so + $(CC) -fsanitize=address -Og -g -o $@ $< -lantiasan -L. + +libantiasan.so: antiasan.c + $(CC) -g -fPIC -c antiasan.c + $(CC) -shared antiasan.o -o libantiasan.so + +.PHINY: run +run: + LD_LIBRARY_PATH=. ./bss_overflow_asan + +.PHONY: clean +clean: + rm bss_overflow_asan antiasan.o libantiasan.so diff --git a/lab5/README.md b/lab5/README.md new file mode 100644 index 0000000..1528ef4 --- /dev/null +++ b/lab5/README.md @@ -0,0 +1,26 @@ +# Lab5 + +## Introduction + +In this lab, you will write a function antoasan to bypass detection of ASan in `antiasan.c`. + +## Preparation (Important!!!) + +1. Sync fork your branch (e.g., `SQLab:311XXXXXX`) +2. `git checkout -b lab5` (**NOT** your student ID !!!) + +## Requirement + +1. (100%) write a function antoasan to bypass detection of ASan in `antiasan.c`. +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 `antiasan.c`. 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. diff --git a/lab5/ans b/lab5/ans new file mode 100644 index 0000000..59ad6ce --- /dev/null +++ b/lab5/ans @@ -0,0 +1,4 @@ +LD_LIBRARY_PATH=. ./bss_overflow_asan +gBadBuf = HAHAHAHAHAHAHAHAHAHAHAH +gS = HAHAHAHAHAHAHAHAHAHAHAH +gS = HAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAH diff --git a/lab5/antiasan.c b/lab5/antiasan.c new file mode 100644 index 0000000..8a8936d --- /dev/null +++ b/lab5/antiasan.c @@ -0,0 +1,6 @@ +#include + +void antiasan(unsigned long addr) +{ + +} diff --git a/lab5/antiasan.h b/lab5/antiasan.h new file mode 100644 index 0000000..3035179 --- /dev/null +++ b/lab5/antiasan.h @@ -0,0 +1,6 @@ +#ifndef HIJACK_H +#define HIJACK_H + +void antiasan(unsigned long); + +#endif diff --git a/lab5/bss_overflow.c b/lab5/bss_overflow.c new file mode 100644 index 0000000..3470cda --- /dev/null +++ b/lab5/bss_overflow.c @@ -0,0 +1,21 @@ +#include +#include +#include "antiasan.h" + +char gS[0x18]; +char gBadBuf[0x87]; + +int main(void) +{ + strcpy(gBadBuf, "HAHAHAHAHAHAHAHAHAHAHAH"); + strcpy(gS, "HAHAHAHAHAHAHAHAHAHAHAH"); + printf("gBadBuf = %s\n", gBadBuf); + printf("gS = %s\n", gS); + antiasan((unsigned long)&gBadBuf); + for (int i = 0; i < 0x10; i += 2) { + gS[0x17 + i] = 'A'; + gS[0x17 + i + 1] = 'H'; + } + printf("gS = %s\n", gS); + return 0; +} diff --git a/lab5/validate.sh b/lab5/validate.sh new file mode 100755 index 0000000..be3d608 --- /dev/null +++ b/lab5/validate.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "bss_overflow.c" && $file != "antiasan.c" && $file != "antiasan.h" && $file != "Makefile" && $file != "README.md" && $file != "validate.sh" && $file != "ans" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab5-XXXXXXXXXX) +answer="" + +cd $tmp_dir + +rm -rf * +cp $solution_path/Makefile . +cp $solution_path/*.c . +cp $solution_path/*.h . +cp $solution_path/ans . + +make +make run > out 2>&1 +result=$(diff --strip-trailing-cr ans out) +if [[ -n $result ]]; then + echo "[!] Expected: " + cat ans + echo "" + echo "[!] Actual: " + cat out + echo "" + exit 1 +else + echo "[V] Pass" +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: From 92e14d777be519a1a8e44589efda650ff332e090 Mon Sep 17 00:00:00 2001 From: YingMuo Date: Sat, 26 Apr 2025 00:08:49 +0800 Subject: [PATCH 16/28] fix: autograding --- .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 9016526..25a89a3 100644 --- a/.github/workflows/lab-autograding.yml +++ b/.github/workflows/lab-autograding.yml @@ -45,7 +45,7 @@ jobs: 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 = ["lab0/lab0.js"]; + const specialChangedFiles = ["lab0/lab0.js", "lab5/antiasan.c"]; if (!changedFiles.every((file) => (allowedFileRegex.test(file) || specialChangedFiles.includes(file)))) { core.setFailed('The PR contains changes to files other than the allowed files.'); } From 672b5c1cb5761c2eb38de5336bbabbe66101f330 Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Thu, 1 May 2025 01:37:35 +0800 Subject: [PATCH 17/28] Update antiasan.c --- lab5/antiasan.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lab5/antiasan.c b/lab5/antiasan.c index 8a8936d..07f53ba 100644 --- a/lab5/antiasan.c +++ b/lab5/antiasan.c @@ -1,6 +1,10 @@ -#include -void antiasan(unsigned long addr) -{ +#include +extern char gS[]; +extern char gBadBuf[]; +extern void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +void antiasan(unsigned long addr) { + __asan_unpoison_memory_region(gS, 0xa7); + __asan_unpoison_memory_region(gBadBuf, 0xa7); } From b839e6182089e70522cb47fffbd6f81385cccff9 Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Thu, 1 May 2025 01:54:29 +0800 Subject: [PATCH 18/28] Update antiasan.c --- lab5/antiasan.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/lab5/antiasan.c b/lab5/antiasan.c index 6904898..e7756bc 100644 --- a/lab5/antiasan.c +++ b/lab5/antiasan.c @@ -1,7 +1,4 @@ - - #include - extern char gS[]; extern char gBadBuf[]; extern void __asan_unpoison_memory_region(void const volatile *addr, size_t size); From 960ed3ad5fc24c43c289186a98800f4e9f22770a Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Thu, 1 May 2025 01:55:12 +0800 Subject: [PATCH 19/28] Update main_test.js --- lab1/main_test.js | 55 +++++++---------------------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/lab1/main_test.js b/lab1/main_test.js index 5df999d..47844d4 100644 --- a/lab1/main_test.js +++ b/lab1/main_test.js @@ -3,60 +3,21 @@ const assert = require('assert'); const { MyClass, Student } = require('./main'); test("Test MyClass's addStudent", () => { - - const myClass = new MyClass(); - const student = new Student(); - - // Valid student case - const studentId = myClass.addStudent(student); - assert.strictEqual(studentId, 0); - - // Invalid input case - const invalidId = myClass.addStudent({}); - assert.strictEqual(invalidId, -1); + // TODO + throw new Error("Test not implemented"); }); test("Test MyClass's getStudentById", () => { - const myClass = new MyClass(); - const student = new Student(); - student.setName("John"); - - const studentId = myClass.addStudent(student); - - // Valid retrieval - const retrievedStudent = myClass.getStudentById(studentId); - assert.ok(retrievedStudent instanceof Student); - assert.strictEqual(retrievedStudent.getName(), "John"); - - // Invalid ID cases - assert.strictEqual(myClass.getStudentById(-1), null); - assert.strictEqual(myClass.getStudentById(101), null); + // TODO + throw new Error("Test not implemented"); }); - test("Test Student's setName", () => { - const student = new Student(); - - // Valid name setting - student.setName("Jane"); - assert.strictEqual(student.getName(), "Jane"); - - // Invalid input cases - student.setName(123); - assert.strictEqual(student.getName(), "Jane"); // Should not change - - student.setName(null); - assert.strictEqual(student.getName(), "Jane"); // Should not change + // TODO + throw new Error("Test not implemented"); }); test("Test Student's getName", () => { - const student = new Student(); - - // Default value case - assert.strictEqual(student.getName(), ''); - - // After setting a valid name - student.setName("Alice"); - assert.strictEqual(student.getName(), "Alice"); + // TODO + throw new Error("Test not implemented"); }); - From f975718cb7e05aa44a2b2651075e9a7ef1135286 Mon Sep 17 00:00:00 2001 From: YingMuo Date: Thu, 1 May 2025 13:08:49 +0800 Subject: [PATCH 20/28] feat: lab6 --- .github/workflows/lab-autograding.yml | 5 +++- lab6/Makefile | 14 +++++++++ lab6/README.md | 29 ++++++++++++++++++ lab6/ans | 6 ++++ lab6/llvm-pass.so.cc | 34 +++++++++++++++++++++ lab6/target.c | 29 ++++++++++++++++++ lab6/validate.sh | 43 +++++++++++++++++++++++++++ 7 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 lab6/Makefile create mode 100644 lab6/README.md create mode 100644 lab6/ans create mode 100644 lab6/llvm-pass.so.cc create mode 100644 lab6/target.c create mode 100755 lab6/validate.sh diff --git a/.github/workflows/lab-autograding.yml b/.github/workflows/lab-autograding.yml index 25a89a3..5be69d2 100644 --- a/.github/workflows/lab-autograding.yml +++ b/.github/workflows/lab-autograding.yml @@ -45,7 +45,7 @@ jobs: 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 = ["lab0/lab0.js", "lab5/antiasan.c"]; + const specialChangedFiles = ["lab0/lab0.js", "lab5/antiasan.c", "lab6/llvm-pass.so.cc"]; if (!changedFiles.every((file) => (allowedFileRegex.test(file) || specialChangedFiles.includes(file)))) { core.setFailed('The PR contains changes to files other than the allowed files.'); } @@ -53,4 +53,7 @@ jobs: - name: Grading run: | cd lab${{ steps.lab.outputs.result }} + if [ ${{ steps.lab.outputs.result }} -eq 6 ]; then + sudo apt install -y llvm-14 + fi ./validate.sh diff --git a/lab6/Makefile b/lab6/Makefile new file mode 100644 index 0000000..bd37e34 --- /dev/null +++ b/lab6/Makefile @@ -0,0 +1,14 @@ +all: target + +llvm-pass.so: llvm-pass.so.cc + clang-14 `llvm-config-14 --cxxflags` -shared -fPIC $< -o $@ + +target: target.c llvm-pass.so + clang-14 `llvm-config-14 --cflags` -fexperimental-new-pass-manager \ + -fpass-plugin=./llvm-pass.so $< -o $@ + +run: + ./target 1 + +clean: + rm -f llvm-pass.so target \ No newline at end of file diff --git a/lab6/README.md b/lab6/README.md new file mode 100644 index 0000000..e6c45af --- /dev/null +++ b/lab6/README.md @@ -0,0 +1,29 @@ +# Lab6 + +## Introduction + +In this lab, you will write a llvm pass to instrument some codes to `target.c` in `llvm-pass.so.cc`. + +## Preparation (Important!!!) + +1. Sync fork your branch (e.g., `SQLab:311XXXXXX`) +2. `git checkout -b lab6` (**NOT** your student ID !!!) + +## Requirement + +Write a llvm pass to instrument some codes to `target.c` in `llvm-pass.so.cc` and satisfy following requirements. +1. (40%) Invoke debug function with the first argument is 48763 in main function. +2. (30%) Overwrite argv[1] to "hayaku... motohayaku!" before checking. +3. (30%) Overwrite argc to 48763 before checking. +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 `llvm-pass.so.cc`. 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. diff --git a/lab6/ans b/lab6/ans new file mode 100644 index 0000000..0d0a0d8 --- /dev/null +++ b/lab6/ans @@ -0,0 +1,6 @@ +./target 1 +deubg mode +argc = 48763 +argv[1] = hayaku... motohayaku! +Your argc is so hayaku! +Suta... basuto... sutorimu! diff --git a/lab6/llvm-pass.so.cc b/lab6/llvm-pass.so.cc new file mode 100644 index 0000000..6c6e17e --- /dev/null +++ b/lab6/llvm-pass.so.cc @@ -0,0 +1,34 @@ +#include "llvm/Passes/PassPlugin.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/IR/IRBuilder.h" + +using namespace llvm; + +struct LLVMPass : public PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM); +}; + +PreservedAnalyses LLVMPass::run(Module &M, ModuleAnalysisManager &MAM) { + LLVMContext &Ctx = M.getContext(); + IntegerType *Int32Ty = IntegerType::getInt32Ty(Ctx); + FunctionCallee debug_func = M.getOrInsertFunction("debug", Int32Ty); + ConstantInt *debug_arg = ConstantInt::get(Int32Ty, 48763); + + for (auto &F : M) { + errs() << "func: " << F.getName() << "\n"; + + } + return PreservedAnalyses::none(); +} + +extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK +llvmGetPassPluginInfo() { + return {LLVM_PLUGIN_API_VERSION, "LLVMPass", "1.0", + [](PassBuilder &PB) { + PB.registerOptimizerLastEPCallback( + [](ModulePassManager &MPM, OptimizationLevel OL) { + MPM.addPass(LLVMPass()); + }); + }}; +} + diff --git a/lab6/target.c b/lab6/target.c new file mode 100644 index 0000000..c13f2b7 --- /dev/null +++ b/lab6/target.c @@ -0,0 +1,29 @@ +#include +#include +#include + +void debug(int id) +{ + if (id == 48763) + printf("deubg mode\n"); + else + printf("bad id!\n"); +} + +int main(int argc, char **argv) +{ + printf("argc = %d\n", argc); + if (argc >= 2) + printf("argv[1] = %s\n", argv[1]); + else + return 0; + if (argc == 48763) + printf("Your argc is so hayaku!\n"); + else + printf("Your argc need to be modohayaku!\n"); + if (strcmp(argv[1], "hayaku... motohayaku!") == 0) + printf("Suta... basuto... sutorimu!\n"); + else + printf("You dead\n"); + return 0; +} diff --git a/lab6/validate.sh b/lab6/validate.sh new file mode 100755 index 0000000..a6128bb --- /dev/null +++ b/lab6/validate.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "llvm-pass.so.cc" && $file != "target.c" && $file != "Makefile" && $file != "README.md" && $file != "validate.sh" && $file != "ans" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab5-XXXXXXXXXX) +answer="" + +cd $tmp_dir + +rm -rf * +cp $solution_path/Makefile . +cp $solution_path/*.c . +cp $solution_path/*.cc . +cp $solution_path/ans . + +make +make run > out 2>&1 +result=$(diff --strip-trailing-cr ans out) +if [[ -n $result ]]; then + echo "[!] Expected: " + cat ans + echo "" + echo "[!] Actual: " + cat out + echo "" + exit 1 +else + echo "[V] Pass" +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: From 139f54ada2eae49142c611b4951fa1562c031b1a Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Wed, 7 May 2025 20:11:04 +0800 Subject: [PATCH 21/28] Update llvm-pass.so.cc --- lab6/llvm-pass.so.cc | 56 +++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/lab6/llvm-pass.so.cc b/lab6/llvm-pass.so.cc index 6c6e17e..11943a0 100644 --- a/lab6/llvm-pass.so.cc +++ b/lab6/llvm-pass.so.cc @@ -1,34 +1,62 @@ #include "llvm/Passes/PassPlugin.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/GlobalVariable.h" using namespace llvm; -struct LLVMPass : public PassInfoMixin { - PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM); -}; +struct LLVMPass : PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM) { + LLVMContext &Ctx = M.getContext(); + + // 1) Declare debug prototype: void debug(i32) + FunctionCallee debugFunc = M.getOrInsertFunction( + "debug", + FunctionType::get(Type::getVoidTy(Ctx), {Type::getInt32Ty(Ctx)}, false) + ); + ConstantInt *const48763 = ConstantInt::get(Type::getInt32Ty(Ctx), 48763); + + // 2) Locate main + if (Function *F = M.getFunction("main")) { + BasicBlock &entryBB = F->getEntryBlock(); + // Insert right after any allocas/PHIs in entry + IRBuilder<> builder(&*entryBB.getFirstInsertionPt()); + + // --- (40%) Call debug(48763) --- + builder.CreateCall(debugFunc, {const48763}); -PreservedAnalyses LLVMPass::run(Module &M, ModuleAnalysisManager &MAM) { - LLVMContext &Ctx = M.getContext(); - IntegerType *Int32Ty = IntegerType::getInt32Ty(Ctx); - FunctionCallee debug_func = M.getOrInsertFunction("debug", Int32Ty); - ConstantInt *debug_arg = ConstantInt::get(Int32Ty, 48763); + // --- (30%) Overwrite argc → 48763 --- + // main signature is: i32 @main(i32 %argc, i8** %argv) + Argument *argcArg = &*F->arg_begin(); + argcArg->replaceAllUsesWith(const48763); - for (auto &F : M) { - errs() << "func: " << F.getName() << "\n"; + // --- (30%) Overwrite argv[1] → "hayaku... motohayaku!" --- + Argument *argvArg = &*(std::next(F->arg_begin())); + // Create a global constant string + Value *strPtr = builder.CreateGlobalStringPtr("hayaku... motohayaku!"); + // Compute pointer to argv[1]: getelementptr i8*, i8** %argv, i64 1 + Value *idx1 = ConstantInt::get(Type::getInt64Ty(Ctx), 1); + Value *ptrToArg1 = builder.CreateInBoundsGEP( + argvArg->getType()->getPointerElementType(), // element type = i8* + argvArg, // base pointer i8** + idx1 + ); + // Store the new string into argv[1] + builder.CreateStore(strPtr, ptrToArg1); + } + return PreservedAnalyses::none(); } - return PreservedAnalyses::none(); -} +}; extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK llvmGetPassPluginInfo() { return {LLVM_PLUGIN_API_VERSION, "LLVMPass", "1.0", [](PassBuilder &PB) { PB.registerOptimizerLastEPCallback( - [](ModulePassManager &MPM, OptimizationLevel OL) { + [](ModulePassManager &MPM, OptimizationLevel) { MPM.addPass(LLVMPass()); }); }}; } - From eee7787ed017b753f90ea2f9bec3e96576bd431c Mon Sep 17 00:00:00 2001 From: YingMuo Date: Thu, 15 May 2025 12:29:31 +0800 Subject: [PATCH 22/28] feat: lab8 --- .github/workflows/lab-autograding.yml | 2 +- lab8/Makefile | 8 +++++ lab8/README.md | 28 +++++++++++++++++ lab8/ans | 2 ++ lab8/chal.c | 33 ++++++++++++++++++++ lab8/solve.py | 11 +++++++ lab8/validate.sh | 43 +++++++++++++++++++++++++++ 7 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 lab8/Makefile create mode 100644 lab8/README.md create mode 100644 lab8/ans create mode 100644 lab8/chal.c create mode 100755 lab8/solve.py create mode 100755 lab8/validate.sh diff --git a/.github/workflows/lab-autograding.yml b/.github/workflows/lab-autograding.yml index 5be69d2..6aa9e0d 100644 --- a/.github/workflows/lab-autograding.yml +++ b/.github/workflows/lab-autograding.yml @@ -45,7 +45,7 @@ jobs: 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 = ["lab0/lab0.js", "lab5/antiasan.c", "lab6/llvm-pass.so.cc"]; + const specialChangedFiles = ["lab0/lab0.js", "lab5/antiasan.c", "lab6/llvm-pass.so.cc", "lab8/solve.py"]; if (!changedFiles.every((file) => (allowedFileRegex.test(file) || specialChangedFiles.includes(file)))) { core.setFailed('The PR contains changes to files other than the allowed files.'); } diff --git a/lab8/Makefile b/lab8/Makefile new file mode 100644 index 0000000..0daff11 --- /dev/null +++ b/lab8/Makefile @@ -0,0 +1,8 @@ +all: + gcc -o chal -no-pie chal.c + +run: + ./solve.py | ./chal + +clean: + rm chal diff --git a/lab8/README.md b/lab8/README.md new file mode 100644 index 0000000..df5a7da --- /dev/null +++ b/lab8/README.md @@ -0,0 +1,28 @@ +# Lab8 + +## Introduction + +In this lab, you will write a angr script in `solve.py` to crack secret key in `chal.c` and get flag. + +## Preparation (Important!!!) + +1. Sync fork your branch (e.g., `SQLab:311XXXXXX`) +2. `git checkout -b lab8` (**NOT** your student ID !!!) + +## Requirement + +(100%) Write a angr script and satisfy following requirements. +1. Explore flag is in stdout or not by angr to solve secret key. +2. Write secret key to stdout, and `validate.sh` pass it to `./chal` to check secret key. +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 `solve.py`. 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. diff --git a/lab8/ans b/lab8/ans new file mode 100644 index 0000000..7e927d7 --- /dev/null +++ b/lab8/ans @@ -0,0 +1,2 @@ +./solve.py | ./chal +Enter the secret key: Correct! The flag is: CTF{symbolic_execution_for_the_win} diff --git a/lab8/chal.c b/lab8/chal.c new file mode 100644 index 0000000..e14b598 --- /dev/null +++ b/lab8/chal.c @@ -0,0 +1,33 @@ +#include +#include +#include + +void gate(const char *input) +{ + if (strlen(input) != 8) { + return; + } + + if ((input[0] ^ input[1]) != 0x55) return; + if ((input[2] + input[3]) != 200) return; + if ((input[4] * 3) != input[5]) return; + if ((input[6] - input[7]) != 1) return; + + if ((input[1] + input[2] - input[3]) != 50) return; + if ((input[5] ^ input[6]) != 0x2A) return; + + puts("Correct! The flag is: CTF{symbolic_execution_for_the_win}"); + exit(0); +} + +int main() +{ + char input[0x10] = {0}; + printf("Enter the secret key: "); + fgets(input, sizeof(input), stdin); + input[strcspn(input, "\n")] = 0; // Strip newline + gate(input); + puts("Wrong key!"); + return 0; +} + diff --git a/lab8/solve.py b/lab8/solve.py new file mode 100755 index 0000000..9ab3ee2 --- /dev/null +++ b/lab8/solve.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 + +import angr,sys + +def main(): + secret_key = b"" + sys.stdout.buffer.write(secret_key) + + +if __name__ == '__main__': + main() diff --git a/lab8/validate.sh b/lab8/validate.sh new file mode 100755 index 0000000..ca98f95 --- /dev/null +++ b/lab8/validate.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "solve.py" && $file != "chal.c" && $file != "Makefile" && $file != "README.md" && $file != "validate.sh" && $file != "ans" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab8-XXXXXXXXXX) +answer="" + +cd $tmp_dir + +rm -rf * +cp $solution_path/Makefile . +cp $solution_path/ans . +cp $solution_path/*.c . +cp $solution_path/*.py . + +make +make run > out +result=$(diff --strip-trailing-cr ans out) +if [[ -n $result ]]; then + echo "[!] Expected: " + cat ans + echo "" + echo "[!] Actual: " + cat out + echo "" + exit 1 +else + echo "[V] Pass" +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: From ceba5b44f08eb3b898dc8ed440614c7f33392248 Mon Sep 17 00:00:00 2001 From: YingMuo Date: Mon, 19 May 2025 18:54:47 +0800 Subject: [PATCH 23/28] fix: lab8 no angr --- .github/workflows/lab-autograding.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/lab-autograding.yml b/.github/workflows/lab-autograding.yml index 6aa9e0d..437bbc3 100644 --- a/.github/workflows/lab-autograding.yml +++ b/.github/workflows/lab-autograding.yml @@ -56,4 +56,7 @@ jobs: if [ ${{ steps.lab.outputs.result }} -eq 6 ]; then sudo apt install -y llvm-14 fi + if [ ${{ steps.lab.outputs.result }} -eq 8 ]; then + python3 -m pip install angr + fi ./validate.sh From 98339279fb9de76afc7743caa8f6f1f81c7a3f2e Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Thu, 22 May 2025 01:12:05 +0800 Subject: [PATCH 24/28] Update solve.py --- lab8/solve.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/lab8/solve.py b/lab8/solve.py index 9ab3ee2..c1c973f 100755 --- a/lab8/solve.py +++ b/lab8/solve.py @@ -1,11 +1,34 @@ #!/usr/bin/env python3 - -import angr,sys +import angr +import claripy +import sys def main(): - secret_key = b"" - sys.stdout.buffer.write(secret_key) + # 1) load the challenge binary + proj = angr.Project('./chal', auto_load_libs=False) + + # 2) create 8 symbolic bytes plus a trailing newline (fgets strips it) + flag_chars = [claripy.BVS(f'c{i}', 8) for i in range(8)] + sym_input = claripy.Concat(*flag_chars, claripy.BVV(b'\n')) + + # 3) set up the initial state with our symbolic stdin + state = proj.factory.entry_state(stdin=sym_input) + + # 4) explore until we hit the “Correct!” branch (and avoid wrong key) + simgr = proj.factory.simulation_manager(state) + simgr.explore( + find=lambda s: b"Correct! The flag is:" in s.posix.dumps(1), + avoid=lambda s: b"Wrong key!" in s.posix.dumps(1) + ) + # 5) extract and print the concrete 8-byte key + if simgr.found: + found = simgr.found[0] + solution = found.solver.eval(claripy.Concat(*flag_chars), cast_to=bytes) + # write exactly the 8-byte key (no extra newline) + sys.stdout.buffer.write(solution) + else: + sys.exit("[-] No solution found.") if __name__ == '__main__': main() From 11b4c3546a81e0470d3ce441067e6b1f2fbd33b4 Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Thu, 22 May 2025 01:22:10 +0800 Subject: [PATCH 25/28] Update solve.py --- lab8/solve.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/lab8/solve.py b/lab8/solve.py index 8a151ed..ebc1cf6 100755 --- a/lab8/solve.py +++ b/lab8/solve.py @@ -1,36 +1,41 @@ #!/usr/bin/env python3 - import angr import claripy import sys def main(): - # 1) load the challenge binary + # 載入二進位,不載入動態函式庫加快速度 proj = angr.Project('./chal', auto_load_libs=False) - # 2) create 8 symbolic bytes plus a trailing newline (fgets strips it) + # 建立 8 個符號字元 flag_chars = [claripy.BVS(f'c{i}', 8) for i in range(8)] - sym_input = claripy.Concat(*flag_chars, claripy.BVV(b'\n')) + flag = claripy.Concat(*flag_chars) + + # 製作帶有符號輸入的初始狀態 + # has_end=True 表示讀到 flag 後即結束輸入 + stdin = angr.SimFileStream(name='stdin', content=flag, has_end=True) + state = proj.factory.full_init_state(stdin=stdin) - # 3) set up the initial state with our symbolic stdin - state = proj.factory.entry_state(stdin=sym_input) + # 限制每個字元為可列印 ASCII(32~126) + for c in flag_chars: + state.solver.add(c >= 0x20) + state.solver.add(c <= 0x7e) - # 4) explore until we hit the “Correct!” branch (and avoid wrong key) simgr = proj.factory.simulation_manager(state) - simgr.explore( - find=lambda s: b"Correct! The flag is:" in s.posix.dumps(1), - avoid=lambda s: b"Wrong key!" in s.posix.dumps(1) - ) - # 5) extract and print the concrete 8-byte key + # 尋找印出「Correct! The flag is」的路徑 + target = b"Correct! The flag is" + simgr.explore(find=lambda s: target in s.posix.dumps(1)) + if simgr.found: found = simgr.found[0] - solution = found.solver.eval(claripy.Concat(*flag_chars), cast_to=bytes) - # write exactly the 8-byte key (no extra newline) + # 求解出具體 key + solution = found.solver.eval(flag, cast_to=bytes) + # 輸出到 stdout,供 validate.sh 傳給 chal sys.stdout.buffer.write(solution) else: - sys.exit("[-] No solution found.") - + print("No solution found.", file=sys.stderr) + sys.exit(1) if __name__ == '__main__': main() From ce50d903f200cb6abae35d10596801700a20e01c Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Thu, 22 May 2025 01:35:55 +0800 Subject: [PATCH 26/28] Update solve.py --- lab8/solve.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lab8/solve.py b/lab8/solve.py index ebc1cf6..06ed551 100755 --- a/lab8/solve.py +++ b/lab8/solve.py @@ -16,10 +16,7 @@ def main(): stdin = angr.SimFileStream(name='stdin', content=flag, has_end=True) state = proj.factory.full_init_state(stdin=stdin) - # 限制每個字元為可列印 ASCII(32~126) - for c in flag_chars: - state.solver.add(c >= 0x20) - state.solver.add(c <= 0x7e) + simgr = proj.factory.simulation_manager(state) @@ -34,7 +31,7 @@ def main(): # 輸出到 stdout,供 validate.sh 傳給 chal sys.stdout.buffer.write(solution) else: - print("No solution found.", file=sys.stderr) + print("No solution found.") sys.exit(1) if __name__ == '__main__': From 59ff1c5ed4c59a55808d6c791e7e9b4c2060cc90 Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Thu, 22 May 2025 01:49:37 +0800 Subject: [PATCH 27/28] Update solve.py --- lab8/solve.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lab8/solve.py b/lab8/solve.py index 06ed551..9b6a534 100755 --- a/lab8/solve.py +++ b/lab8/solve.py @@ -8,7 +8,7 @@ def main(): proj = angr.Project('./chal', auto_load_libs=False) # 建立 8 個符號字元 - flag_chars = [claripy.BVS(f'c{i}', 8) for i in range(8)] + flag_chars = [claripy.BVS(f'byte_{i}', 8) for i in range(8)] flag = claripy.Concat(*flag_chars) # 製作帶有符號輸入的初始狀態 @@ -21,7 +21,7 @@ def main(): simgr = proj.factory.simulation_manager(state) # 尋找印出「Correct! The flag is」的路徑 - target = b"Correct! The flag is" + target = b"Correct!" simgr.explore(find=lambda s: target in s.posix.dumps(1)) if simgr.found: From f91818ccdff9e83d69a13b22c6f33ca0799ebc74 Mon Sep 17 00:00:00 2001 From: banana1715 <82526396+banana1715@users.noreply.github.com> Date: Thu, 22 May 2025 01:55:48 +0800 Subject: [PATCH 28/28] Update solve.py --- lab8/solve.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/lab8/solve.py b/lab8/solve.py index 9b6a534..d15b0d0 100755 --- a/lab8/solve.py +++ b/lab8/solve.py @@ -4,34 +4,30 @@ import sys def main(): - # 載入二進位,不載入動態函式庫加快速度 - proj = angr.Project('./chal', auto_load_libs=False) + proj = angr.Project('./chal', auto_load_libs=False) - # 建立 8 個符號字元 - flag_chars = [claripy.BVS(f'byte_{i}', 8) for i in range(8)] - flag = claripy.Concat(*flag_chars) + #建立8-bit輸入 + sym_chars = [claripy.BVS(f'byte_{i}', 8) for i in range(8)] + sym_input = claripy.Concat(*sym_chars) - # 製作帶有符號輸入的初始狀態 - # has_end=True 表示讀到 flag 後即結束輸入 - stdin = angr.SimFileStream(name='stdin', content=flag, has_end=True) - state = proj.factory.full_init_state(stdin=stdin) + #初始化執行狀態並模擬stdin輸入 + state = proj.factory.full_init_state( + stdin = angr.SimFileStream(name='stdin', content=sym_input, has_end=True) + ) + #建立模擬器並開始搜尋個別狀態 + simgr = proj.factory.simgr(state) + simgr.explore( + find = lambda s:b"Correct!" in s.posix.dumps(1) + ) - - simgr = proj.factory.simulation_manager(state) - - # 尋找印出「Correct! The flag is」的路徑 - target = b"Correct!" - simgr.explore(find=lambda s: target in s.posix.dumps(1)) - + #找到則輸出結果,否則輸出 "No solution found!" if simgr.found: found = simgr.found[0] - # 求解出具體 key - solution = found.solver.eval(flag, cast_to=bytes) - # 輸出到 stdout,供 validate.sh 傳給 chal - sys.stdout.buffer.write(solution) + secret_key = found.solver.eval(sym_input, cast_to=bytes) + sys.stdout.buffer.write(secret_key) else: - print("No solution found.") + print("No solution found!") sys.exit(1) if __name__ == '__main__':