ci: add test & benchmark

pull/340/head
undefined 3 years ago
parent 5c1217dc60
commit d589217a75

@ -45,10 +45,18 @@ jobs:
- name: Build And Lint
run: |
yarn build:ui:gulp
parallel --tty -j+0 yarn ::: lint:ci lint:ui:ci build build:ui:production:webpack
parallel --tty -j+0 yarn ::: lint:ci lint:ui:ci build build:ui:production:webpack test
- name: Publish
if: ${{ github.event_name == 'push' }}
run: node build/publish.js
- name: Benchmark
run: yarn benchmark
- name: Benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
name: Benchmark
tool: customBiggerIsBetter
output-file-path: benchmark.json
# web:
# needs: build
# permissions:

1
.gitignore vendored

@ -11,6 +11,7 @@ dist/
*.tsbuildinfo
*.hydro
.coverage
benchmark.json
globalConfig.json
*.out
.clinic/

@ -14,6 +14,8 @@
"build:ui:dev": "yarn build:ui:gulp && node --trace-deprecation packages/ui-default/build --dev",
"build:ui:production": "yarn build:ui:gulp && node packages/ui-default/build --production",
"build:ui:production:webpack": "node packages/ui-default/build --production",
"test": "mocha",
"benchmark": "cross-env BENCHMARK=true mocha",
"lint": "eslint packages --ext ts --fix",
"lint:ci": "eslint packages --ext ts",
"lint:ui": "yarn workspace @hydrooj/ui-default eslint --ext .js,.ts,.jsx,.tsx . --fix",
@ -28,30 +30,37 @@
"version": "1.0.0",
"license": "AGPL-3.0-only",
"devDependencies": {
"@types/autocannon": "^7.6.1",
"@types/cross-spawn": "^6.0.2",
"@types/node": "^17.0.21",
"@types/node": "^17.0.23",
"@types/semver": "^7.3.9",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "5.14.0",
"@typescript-eslint/parser": "5.14.0",
"autocannon": "^7.8.1",
"cac": "^6.7.12",
"cross-env": "^7.0.3",
"cross-spawn": "^7.0.3",
"esbuild": "0.14.3",
"eslint": "^8.11.0",
"eslint": "^8.12.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "16.1.1",
"eslint-import-resolver-typescript": "2.5.0",
"eslint-import-resolver-webpack": "^0.13.2",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.29.3",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-simple-import-sort": "7.0.0",
"fs-extra": "^10.0.1",
"globby": "11.1.0",
"latest-version": "^6.0.0",
"mocha": "^9.2.2",
"mongo-mock": "^4.1.0",
"mongodb": "^3.7.3",
"mongodb-memory-server": "^8.4.2",
"ora": "5.4.1",
"semver": "^7.3.5",
"supertest": "^6.2.2",
"typedoc": "^0.22.13",
"typescript": "4.6.2"
}

@ -8,7 +8,7 @@
"preferUnplugged": true,
"dependencies": {
"js-yaml": "^4.1.0",
"superagent": "^7.1.1"
"superagent": "^7.1.2"
},
"devDependencies": {
"@types/js-yaml": "^4.0.5"

@ -6,7 +6,7 @@
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0-or-later",
"dependencies": {
"maxmind": "^4.3.5"
"maxmind": "^4.3.6"
},
"preferUnplugged": true,
"scripts": {

@ -23,7 +23,7 @@
"devDependencies": {
"@types/fs-extra": "^9.0.13",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.179",
"@types/lodash": "^4.14.181",
"@types/shell-quote": "^1.7.1",
"@types/ws": "^8.5.3"
},

@ -12,20 +12,20 @@
},
"preferUnplugged": true,
"dependencies": {
"@graphql-tools/schema": "^8.3.2",
"@graphql-tools/schema": "^8.3.6",
"@hydrooj/utils": "workspace:*",
"adm-zip": "0.5.5",
"ajv": "^8.10.0",
"ajv": "^8.11.0",
"ansi_up": "^5.1.0",
"cac": "^6.7.12",
"cookies": "^0.8.0",
"detect-browser": "^5.3.0",
"emoji-regex": "^10.0.1",
"emoji-regex": "^10.1.0",
"emojis-list": "2.1.0",
"esbuild": "0.14.3",
"fs-extra": "^10.0.1",
"graphql": "^16.3.0",
"graphql-scalars": "1.15.0",
"graphql-scalars": "1.17.0",
"js-yaml": "^4.1.0",
"koa": "^2.13.4",
"koa-body": "^4.2.0",
@ -34,40 +34,39 @@
"koa-router": "^10.1.1",
"koa-static-cache": "^5.1.4",
"lodash": "^4.17.21",
"lru-cache": "7.4.4",
"mime-types": "^2.1.34",
"lru-cache": "7.7.3",
"mime-types": "^2.1.35",
"minio": "7.0.25",
"moment-timezone": "^0.5.34",
"mongodb": "^3.7.3",
"nanoid": "^3.3.1",
"nodemailer": "^6.7.2",
"nanoid": "^3.3.2",
"nodemailer": "^6.7.3",
"notp": "^2.0.3",
"p-queue": "^7.2.0",
"reflect-metadata": "^0.1.13",
"require-resolve-hook": "^1.1.0",
"semver": "^7.3.5",
"serialize-javascript": "^6.0.0",
"sockjs": "^0.3.24",
"source-map-support": "^0.5.21",
"superagent": "^7.1.1",
"superagent": "^7.1.2",
"thirty-two": "^1.0.2",
"tx2": "^1.0.5"
},
"devDependencies": {
"@types/adm-zip": "^0.4.34",
"@types/adm-zip": "^0.5.0",
"@types/fs-extra": "^9.0.13",
"@types/js-yaml": "^4.0.5",
"@types/koa": "^2.13.4",
"@types/koa-compress": "^4.0.3",
"@types/koa-router": "^7.4.4",
"@types/koa-static-cache": "^5.1.1",
"@types/lodash": "^4.14.179",
"@types/lru-cache": "^7.4.0",
"@types/lodash": "^4.14.181",
"@types/lru-cache": "^7.6.1",
"@types/mime-types": "^2.1.1",
"@types/minio": "^7.0.12",
"@types/mongodb": "^3.6.20",
"@types/nodemailer": "^6.4.4",
"@types/notp": "^2.0.1",
"@types/notp": "^2.0.2",
"@types/semver": "^7.3.9",
"@types/serialize-javascript": "^5.0.2",
"@types/sockjs": "^0.3.33",

@ -39,7 +39,7 @@ export function registerResolver(
registerValue(typeName, key, value, description);
const wrappedFunc = async (arg, ctx, info) => {
const res = await func(arg, ctx, info);
if (typeof res !== 'object') return res;
if (typeof res !== 'object' || res === null) return res;
const node = value.includes('!') ? value.split('!')[0] : value;
if (handlers[node]) Object.assign(res, handlers[node]);
ctx.parent = res;

@ -1,7 +1,6 @@
/* eslint-disable simple-import-sort/imports */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-eval */
import 'reflect-metadata';
import './init';
import './interface';
import path from 'path';

@ -12,6 +12,7 @@ const coll = db.collection('task');
const collEvent = db.collection('event');
async function getFirst(query: FilterQuery<Task>) {
if (process.env.CI) return null;
const q = { ...query };
q.executeAfter = q.executeAfter || { $lt: new Date() };
const res = await coll.findOneAndDelete(q, { sort: { priority: -1 } });

@ -34,8 +34,13 @@ class MongoService implements BaseService {
}
async start(opts: MongoConfig) {
let mongourl = MongoService.buildUrl(opts);
if (process.env.CI) {
const { MongoMemoryServer } = require('mongodb-memory-server');
const mongod = await MongoMemoryServer.create();
mongourl = mongod.getUri();
}
this.opts = opts;
const mongourl = MongoService.buildUrl(opts);
this.client = await MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true });
this.db = this.client.db(opts.name);
await bus.parallel('database/connect', this.db);

@ -13,6 +13,6 @@
"hydrooj": "*"
},
"dependencies": {
"superagent": "^7.1.1"
"superagent": "^7.1.2"
}
}

@ -13,6 +13,6 @@
"hydrooj": "*"
},
"dependencies": {
"superagent": "^7.1.1"
"superagent": "^7.1.2"
}
}

@ -8,6 +8,6 @@
"license": "AGPL-3.0-or-later",
"preferUnplugged": true,
"dependencies": {
"superagent": "^7.1.1"
"superagent": "^7.1.2"
}
}

@ -9,6 +9,6 @@
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0-or-later",
"dependencies": {
"sonic-channel": "^1.2.6"
"sonic-channel": "^1.2.7"
}
}

@ -10,20 +10,20 @@
"lint": "eslint ."
},
"devDependencies": {
"@blueprintjs/core": "^3.53.0",
"@fontsource/dm-mono": "^4.5.4",
"@fontsource/fira-code": "^4.5.5",
"@fontsource/inconsolata": "^4.5.2",
"@fontsource/jetbrains-mono": "^4.5.3",
"@fontsource/pt-mono": "^4.5.3",
"@fontsource/roboto-mono": "^4.5.3",
"@fontsource/source-code-pro": "^4.5.4",
"@fontsource/ubuntu-mono": "^4.5.4",
"@blueprintjs/core": "^3.54.0",
"@fontsource/dm-mono": "^4.5.7",
"@fontsource/fira-code": "^4.5.8",
"@fontsource/inconsolata": "^4.5.4",
"@fontsource/jetbrains-mono": "^4.5.5",
"@fontsource/pt-mono": "^4.5.5",
"@fontsource/roboto-mono": "^4.5.5",
"@fontsource/source-code-pro": "^4.5.6",
"@fontsource/ubuntu-mono": "^4.5.6",
"@hydrooj/utils": "workspace:*",
"@types/gulp-if": "^0.0.34",
"@types/jquery": "^3.5.14",
"@types/json-schema": "^7.0.9",
"@types/katex": "^0.11.1",
"@types/json-schema": "^7.0.11",
"@types/katex": "^0.14.0",
"@types/qrcode": "^1.4.2",
"@types/redux-logger": "^3.0.9",
"@types/sockjs-client": "^1.5.1",
@ -39,15 +39,15 @@
"copy-webpack-plugin": "^6.4.1",
"css-loader": "^4.3.0",
"diff-dom": "^4.2.3",
"echarts": "^5.3.1",
"echarts": "^5.3.2",
"emojis-keywords": "2.0.0",
"emojis-list": "2.1.0",
"esbuild-loader": "^2.18.0",
"eslint": "^8.11.0",
"eslint": "^8.12.0",
"fancy-log": "^2.0.0",
"file-loader": "^6.2.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"graphiql": "^1.7.1",
"graphiql": "^1.8.3",
"gulp": "^4.0.2",
"gulp-cli": "^2.3.0",
"gulp-iconfont": "^11.0.1",
@ -64,7 +64,7 @@
"monaco-editor-nls": "^2.0.0",
"monaco-editor-webpack-plugin": "^7.0.1",
"monaco-themes": "^0.4.0",
"nanoid": "^3.3.1",
"nanoid": "^3.3.2",
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
"pickadate": "^3.6.4",
@ -78,7 +78,7 @@
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.6",
"react-redux": "^7.2.8",
"react-split-pane": "^0.1.92",
"reconnecting-websocket": "^4.4.0",
"redux": "^4.1.2",
@ -91,7 +91,7 @@
"speed-measure-webpack-plugin": "^1.5.0",
"sticky-kit": "^1.1.3",
"style-loader": "^2.0.0",
"stylus": "^0.56.0",
"stylus": "^0.57.0",
"stylus-loader": "^3.0.2",
"tether": "^1.4.7",
"tether-drop": "^1.4.2",
@ -99,7 +99,7 @@
"through2": "^4.0.2",
"timeago-react": "^3.0.4",
"timeago.js": "^4.0.2",
"vditor": "^3.8.12",
"vditor": "^3.8.13",
"vinyl-buffer": "^1.0.1",
"web-streams-polyfill": "^3.2.0",
"webpack": "^4.46.0",
@ -112,7 +112,7 @@
"fs-extra": "^10.0.1",
"js-yaml": "^4.1.0",
"jsesc": "^3.0.2",
"katex": "^0.15.2",
"katex": "^0.15.3",
"lodash": "^4.17.21",
"markdown-it": "^12.3.2",
"markdown-it-anchor": "^8.4.1",
@ -124,7 +124,7 @@
"mongodb": "^3.7.3",
"nunjucks": "^3.2.3",
"p-queue": "^7.2.0",
"react-query": "^3.34.16",
"react-query": "^3.34.19",
"streamsaver": "^2.0.6",
"xss": "^1.0.11"
}

@ -12,7 +12,7 @@
"js-yaml": "^4.1.0",
"moment": "^2.29.1",
"mongodb": "^3.7.3",
"systeminformation": "^5.11.8"
"systeminformation": "^5.11.9"
},
"devDependencies": {
"@types/fs-extra": "^9.0.13",

@ -12,16 +12,16 @@
"chrome-finder": "^1.0.7",
"jsdom": "^19.0.0",
"lodash": "^4.17.21",
"puppeteer-core": "^13.5.1",
"puppeteer-core": "^13.5.2",
"puppeteer-extra": "^3.2.3",
"puppeteer-extra-plugin-portal": "^3.0.1",
"puppeteer-extra-plugin-stealth": "^2.9.0",
"superagent": "^7.1.1",
"superagent": "^7.1.2",
"superagent-proxy": "^3.0.0"
},
"devDependencies": {
"@types/jsdom": "^16.2.14",
"@types/lodash": "^4.14.179",
"@types/lodash": "^4.14.181",
"@types/superagent": "^4.1.15",
"@types/superagent-proxy": "^3.0.0"
}

@ -0,0 +1,3 @@
process.env.CI = true;
require('hydrooj/bin/hydrooj');
require('./main');

@ -0,0 +1,78 @@
import assert from 'assert';
import autocannon from 'autocannon';
import { writeFileSync } from 'fs-extra';
import * as supertest from 'supertest';
import * as bus from 'hydrooj/src/service/bus';
const Root = {
username: 'root',
password: '123456',
creditionals: null,
};
describe('App', () => {
let agent: supertest.SuperAgentTest;
before('init', function init(done) {
this.timeout(30000);
bus.on('app/started', () => setTimeout(() => {
agent = supertest.agent(require('hydrooj/src/service/server').server);
done();
}, 2000));
});
const routes = ['/', '/api', '/p', '/contest', '/homework', '/user/1', '/training'];
routes.forEach((route) => it(`GET ${route}`, () => agent.get(route).expect(200)));
it('API user', async () => {
await agent.get('/api?{user(id:1){uname}}').expect({ data: { user: { uname: 'Hydro' } } });
await agent.get('/api?{user(id:2){uname}}').expect({ data: { user: null } });
});
it('Create User', async () => {
const redirect = await agent.post('/register')
.send({ mail: 'test@example.com' })
.expect(302)
.then((res) => res.headers.location);
await agent.post(redirect)
.send({ uname: Root.username, password: Root.password, verifyPassword: Root.password })
.expect(302);
});
it('Login', async () => {
const cookie = await agent.post('/login')
.send({ uname: Root.username, password: Root.password })
.expect(302)
.then((res) => res.headers['set-cookie']);
Root.creditionals = cookie;
});
it('API registered user', async () => {
await agent.get('/api?{user(id:2){uname}}').expect({ data: { user: { uname: 'root' } } });
});
// TODO add more tests
const results: Record<string, autocannon.Result> = {};
if (process.env.BENCHMARK) {
routes.forEach((route) => it(`Performance test ${route}`, async function test() {
this.timeout(60000);
await global.Hydro.model.system.set('limit.global', 99999);
const result = await autocannon({ url: `http://localhost:8888${route}` });
assert(result.errors === 0, `test ${route} returns errors`);
results[route] = result;
}));
}
after(() => {
const metrics = [];
for (const key in results) {
metrics.push({
name: `Benchmark - ${key} - Req/sec`,
unit: 'Req/sec',
value: results[key].requests.average,
});
}
writeFileSync('./benchmark.json', JSON.stringify(metrics, null, 2));
setTimeout(() => process.exit(0), 1000);
});
});
Loading…
Cancel
Save