ic-mops 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/bundle/cli.tgz +0 -0
- package/check-requirements.ts +1 -1
- package/cli.ts +29 -5
- package/commands/bench.ts +1 -1
- package/commands/install/install-all.ts +28 -6
- package/commands/install/install-dep.ts +5 -4
- package/commands/install/install-deps.ts +3 -2
- package/commands/install/install-local-dep.ts +11 -5
- package/commands/install/install-mops-dep.ts +8 -5
- package/commands/replica.ts +33 -3
- package/commands/sources.ts +1 -1
- package/commands/sync.ts +4 -19
- package/commands/test/mmf1.ts +4 -0
- package/commands/test/reporters/silent-reporter.ts +22 -4
- package/commands/test/test.ts +74 -10
- package/commands/watch/deployer.ts +155 -0
- package/commands/watch/error-checker.ts +87 -0
- package/commands/watch/generator.ts +99 -0
- package/commands/watch/globMoFiles.ts +16 -0
- package/commands/watch/parseDfxJson.ts +64 -0
- package/commands/watch/tester.ts +81 -0
- package/commands/watch/warning-checker.ts +133 -0
- package/commands/watch/watch.ts +90 -0
- package/declarations/main/main.did +16 -10
- package/declarations/main/main.did.d.ts +19 -10
- package/declarations/main/main.did.js +25 -11
- package/dist/check-requirements.js +1 -1
- package/dist/cli.js +26 -5
- package/dist/commands/bench.js +1 -1
- package/dist/commands/install/install-all.d.ts +2 -1
- package/dist/commands/install/install-all.js +24 -6
- package/dist/commands/install/install-dep.d.ts +2 -1
- package/dist/commands/install/install-dep.js +4 -4
- package/dist/commands/install/install-deps.d.ts +2 -1
- package/dist/commands/install/install-deps.js +2 -2
- package/dist/commands/install/install-local-dep.d.ts +2 -1
- package/dist/commands/install/install-local-dep.js +9 -4
- package/dist/commands/install/install-mops-dep.d.ts +2 -1
- package/dist/commands/install/install-mops-dep.js +7 -5
- package/dist/commands/replica.d.ts +2 -2
- package/dist/commands/replica.js +26 -3
- package/dist/commands/sources.d.ts +1 -1
- package/dist/commands/sources.js +1 -1
- package/dist/commands/sync.js +3 -18
- package/dist/commands/test/mmf1.d.ts +1 -0
- package/dist/commands/test/mmf1.js +3 -0
- package/dist/commands/test/reporters/silent-reporter.d.ts +6 -1
- package/dist/commands/test/reporters/silent-reporter.js +18 -5
- package/dist/commands/test/test.d.ts +1 -1
- package/dist/commands/test/test.js +62 -10
- package/dist/commands/watch/deployer.d.ts +24 -0
- package/dist/commands/watch/deployer.js +125 -0
- package/dist/commands/watch/error-checker.d.ts +13 -0
- package/dist/commands/watch/error-checker.js +76 -0
- package/dist/commands/watch/generator.d.ts +21 -0
- package/dist/commands/watch/generator.js +79 -0
- package/dist/commands/watch/globMoFiles.d.ts +1 -0
- package/dist/commands/watch/globMoFiles.js +14 -0
- package/dist/commands/watch/parseDfxJson.d.ts +2 -0
- package/dist/commands/watch/parseDfxJson.js +22 -0
- package/dist/commands/watch/tester.d.ts +19 -0
- package/dist/commands/watch/tester.js +63 -0
- package/dist/commands/watch/warning-checker.d.ts +20 -0
- package/dist/commands/watch/warning-checker.js +111 -0
- package/dist/commands/watch/watch.d.ts +7 -0
- package/dist/commands/watch/watch.js +79 -0
- package/dist/declarations/main/main.did +16 -10
- package/dist/declarations/main/main.did.d.ts +19 -10
- package/dist/declarations/main/main.did.js +25 -11
- package/dist/helpers/get-moc-path.d.ts +1 -1
- package/dist/helpers/get-moc-path.js +17 -3
- package/dist/helpers/get-moc-version.d.ts +1 -1
- package/dist/helpers/get-moc-version.js +17 -5
- package/dist/integrity.d.ts +20 -0
- package/dist/integrity.js +42 -10
- package/dist/mops.d.ts +2 -1
- package/dist/mops.js +13 -10
- package/dist/package.json +3 -2
- package/dist/parallel.d.ts +1 -1
- package/dist/resolve-packages.js +7 -0
- package/dist/templates/mops-test.yml +4 -2
- package/dist/vessel.d.ts +2 -1
- package/dist/vessel.js +4 -1
- package/helpers/get-moc-path.ts +19 -3
- package/helpers/get-moc-version.ts +17 -5
- package/integrity.ts +56 -13
- package/mops.ts +15 -11
- package/package.json +3 -2
- package/parallel.ts +2 -2
- package/resolve-packages.ts +9 -0
- package/templates/mops-test.yml +4 -2
- package/vessel.ts +5 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
type InstallLocalDepOptions = {
|
|
2
2
|
verbose?: boolean;
|
|
3
3
|
silent?: boolean;
|
|
4
|
+
ignoreTransitive?: boolean;
|
|
4
5
|
};
|
|
5
|
-
export declare function installLocalDep(pkg: string, pkgPath?: string, { verbose, silent }?: InstallLocalDepOptions): Promise<boolean>;
|
|
6
|
+
export declare function installLocalDep(pkg: string, pkgPath?: string, { verbose, silent, ignoreTransitive }?: InstallLocalDepOptions): Promise<boolean>;
|
|
6
7
|
export {};
|
|
@@ -5,7 +5,7 @@ import { getRootDir, readConfig } from '../../mops.js';
|
|
|
5
5
|
import { installDeps } from './install-deps.js';
|
|
6
6
|
// skip install and just find non-local dependencies to install
|
|
7
7
|
// pkgPath should be relative to the current root dir or absolute
|
|
8
|
-
export async function installLocalDep(pkg, pkgPath = '', { verbose, silent } = {}) {
|
|
8
|
+
export async function installLocalDep(pkg, pkgPath = '', { verbose, silent, ignoreTransitive } = {}) {
|
|
9
9
|
if (!silent) {
|
|
10
10
|
let logUpdate = createLogUpdate(process.stdout, { showCursor: true });
|
|
11
11
|
logUpdate(`Local dependency ${pkg} = "${pkgPath}"`);
|
|
@@ -17,7 +17,12 @@ export async function installLocalDep(pkg, pkgPath = '', { verbose, silent } = {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
// install dependencies
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
if (!ignoreTransitive) {
|
|
21
|
+
let dir = path.resolve(getRootDir(), pkgPath);
|
|
22
|
+
let config = readConfig(path.join(dir, 'mops.toml'));
|
|
23
|
+
return installDeps(Object.values(config.dependencies || {}), { silent, verbose }, pkgPath);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
23
28
|
}
|
|
@@ -3,6 +3,7 @@ type InstallMopsDepOptions = {
|
|
|
3
3
|
silent?: boolean;
|
|
4
4
|
dep?: boolean;
|
|
5
5
|
threads?: number;
|
|
6
|
+
ignoreTransitive?: boolean;
|
|
6
7
|
};
|
|
7
|
-
export declare function installMopsDep(pkg: string, version?: string, { verbose, silent, dep, threads }?: InstallMopsDepOptions): Promise<boolean>;
|
|
8
|
+
export declare function installMopsDep(pkg: string, version?: string, { verbose, silent, dep, threads, ignoreTransitive }?: InstallMopsDepOptions): Promise<boolean>;
|
|
8
9
|
export {};
|
|
@@ -13,7 +13,7 @@ import { getDepCacheDir, getMopsDepCacheName, isDepCached } from '../../cache.js
|
|
|
13
13
|
import { downloadFile, getPackageFilesInfo } from '../../api/downloadPackageFiles.js';
|
|
14
14
|
import { installDeps } from './install-deps.js';
|
|
15
15
|
import { getDepName } from '../../helpers/get-dep-name.js';
|
|
16
|
-
export async function installMopsDep(pkg, version = '', { verbose, silent, dep, threads } = {}) {
|
|
16
|
+
export async function installMopsDep(pkg, version = '', { verbose, silent, dep, threads, ignoreTransitive } = {}) {
|
|
17
17
|
threads = threads || 12;
|
|
18
18
|
let depName = getDepName(pkg);
|
|
19
19
|
if (!checkConfigFile()) {
|
|
@@ -90,10 +90,12 @@ export async function installMopsDep(pkg, version = '', { verbose, silent, dep,
|
|
|
90
90
|
logUpdate.clear();
|
|
91
91
|
}
|
|
92
92
|
// install dependencies
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
if (!ignoreTransitive) {
|
|
94
|
+
let config = readConfig(path.join(cacheDir, 'mops.toml'));
|
|
95
|
+
let res = await installDeps(Object.values(config.dependencies || {}), { silent, verbose });
|
|
96
|
+
if (!res) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
97
99
|
}
|
|
98
100
|
return true;
|
|
99
101
|
}
|
|
@@ -25,12 +25,12 @@ export declare class Replica {
|
|
|
25
25
|
start({ type, dir, verbose, silent }?: StartOptions): Promise<void>;
|
|
26
26
|
_attachCanisterLogHandler(proc: ChildProcessWithoutNullStreams): void;
|
|
27
27
|
stop(sigint?: boolean): Promise<void>;
|
|
28
|
-
deploy(name: string, wasm: string, idlFactory: IDL.InterfaceFactory, cwd?: string): Promise<{
|
|
28
|
+
deploy(name: string, wasm: string, idlFactory: IDL.InterfaceFactory, cwd?: string, signal?: AbortSignal): Promise<{
|
|
29
29
|
cwd: string;
|
|
30
30
|
canisterId: string;
|
|
31
31
|
actor: any;
|
|
32
32
|
stream: PassThrough;
|
|
33
|
-
}>;
|
|
33
|
+
} | undefined>;
|
|
34
34
|
getActor(name: string): unknown;
|
|
35
35
|
getCanister(name: string): {
|
|
36
36
|
cwd: string;
|
package/dist/commands/replica.js
CHANGED
|
@@ -3,6 +3,7 @@ import { execSync, spawn } from 'node:child_process';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import { PassThrough } from 'node:stream';
|
|
6
|
+
import { spawn as spawnAsync } from 'promisify-child-process';
|
|
6
7
|
import { Actor, HttpAgent } from '@dfinity/agent';
|
|
7
8
|
import { PocketIc, PocketIcServer } from 'pic-ic';
|
|
8
9
|
import { readConfig } from '../mops.js';
|
|
@@ -97,7 +98,7 @@ export class Replica {
|
|
|
97
98
|
await this.pocketIcServer.stop();
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
|
-
async deploy(name, wasm, idlFactory, cwd = process.cwd()) {
|
|
101
|
+
async deploy(name, wasm, idlFactory, cwd = process.cwd(), signal) {
|
|
101
102
|
if (this.type === 'dfx') {
|
|
102
103
|
// prepare dfx.json for current canister
|
|
103
104
|
let dfxJson = path.join(this.dir, 'dfx.json');
|
|
@@ -111,8 +112,24 @@ export class Replica {
|
|
|
111
112
|
}
|
|
112
113
|
fs.mkdirSync(this.dir, { recursive: true });
|
|
113
114
|
fs.writeFileSync(dfxJson, JSON.stringify(newDfxJsonData, null, 2));
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
await spawnAsync('dfx', ['deploy', name, '--mode', 'reinstall', '--yes', '--identity', 'anonymous'], { cwd: this.dir, signal, stdio: this.verbose ? 'pipe' : ['pipe', 'ignore', 'pipe'] }).catch((error) => {
|
|
116
|
+
if (error.code === 'ABORT_ERR') {
|
|
117
|
+
return { stderr: '' };
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
});
|
|
121
|
+
if (signal?.aborted) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
await spawnAsync('dfx', ['ledger', 'fabricate-cycles', '--canister', name, '--t', '100'], { cwd: this.dir, signal, stdio: this.verbose ? 'pipe' : ['pipe', 'ignore', 'pipe'] }).catch((error) => {
|
|
125
|
+
if (error.code === 'ABORT_ERR') {
|
|
126
|
+
return { stderr: '' };
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
});
|
|
130
|
+
if (signal?.aborted) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
116
133
|
let canisterId = execSync(`dfx canister id ${name}`, { cwd: this.dir }).toString().trim();
|
|
117
134
|
let actor = Actor.createActor(idlFactory, {
|
|
118
135
|
agent: await HttpAgent.create({
|
|
@@ -134,7 +151,13 @@ export class Replica {
|
|
|
134
151
|
idlFactory,
|
|
135
152
|
wasm,
|
|
136
153
|
});
|
|
154
|
+
if (signal?.aborted) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
137
157
|
await this.pocketIc.addCycles(canisterId, 1_000_000_000_000);
|
|
158
|
+
if (signal?.aborted) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
138
161
|
this.canisters[name] = {
|
|
139
162
|
cwd,
|
|
140
163
|
canisterId: canisterId.toText(),
|
package/dist/commands/sources.js
CHANGED
package/dist/commands/sync.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import process from 'node:process';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { execSync } from 'node:child_process';
|
|
4
3
|
import { globSync } from 'glob';
|
|
@@ -7,6 +6,7 @@ import { checkConfigFile, getRootDir, readConfig } from '../mops.js';
|
|
|
7
6
|
import { add } from './add.js';
|
|
8
7
|
import { remove } from './remove.js';
|
|
9
8
|
import { checkIntegrity } from '../integrity.js';
|
|
9
|
+
import { getMocPath } from '../helpers/get-moc-path.js';
|
|
10
10
|
export async function sync({ lock } = {}) {
|
|
11
11
|
if (!checkConfigFile()) {
|
|
12
12
|
return;
|
|
@@ -35,24 +35,9 @@ let ignore = [
|
|
|
35
35
|
'**/.git/**',
|
|
36
36
|
'**/.mops/**',
|
|
37
37
|
];
|
|
38
|
-
let mocPath = '';
|
|
39
|
-
function getMocPath() {
|
|
40
|
-
if (!mocPath) {
|
|
41
|
-
mocPath = process.env.DFX_MOC_PATH || '';
|
|
42
|
-
}
|
|
43
|
-
if (!mocPath) {
|
|
44
|
-
try {
|
|
45
|
-
mocPath = execSync('dfx cache show').toString().trim() + '/moc';
|
|
46
|
-
}
|
|
47
|
-
catch { }
|
|
48
|
-
}
|
|
49
|
-
if (!mocPath) {
|
|
50
|
-
mocPath = 'moc';
|
|
51
|
-
}
|
|
52
|
-
return mocPath;
|
|
53
|
-
}
|
|
54
38
|
async function getUsedPackages() {
|
|
55
39
|
let rootDir = getRootDir();
|
|
40
|
+
let mocPath = getMocPath();
|
|
56
41
|
let files = globSync('**/*.mo', {
|
|
57
42
|
cwd: rootDir,
|
|
58
43
|
nocase: true,
|
|
@@ -60,7 +45,7 @@ async function getUsedPackages() {
|
|
|
60
45
|
});
|
|
61
46
|
let packages = new Set;
|
|
62
47
|
for (let file of files) {
|
|
63
|
-
let deps = execSync(`${
|
|
48
|
+
let deps = execSync(`${mocPath} --print-deps ${path.join(rootDir, file)}`).toString().trim().split('\n');
|
|
64
49
|
for (let dep of deps) {
|
|
65
50
|
if (dep.startsWith('mo:') && !dep.startsWith('mo:prim') && !dep.startsWith('mo:⛔')) {
|
|
66
51
|
packages.add(dep.replace(/^mo:([^/]+).*$/, '$1'));
|
|
@@ -18,6 +18,7 @@ export declare class MMF1 {
|
|
|
18
18
|
constructor(strategy: Strategy, file: string);
|
|
19
19
|
_log(type: MessageType, ...args: string[]): void;
|
|
20
20
|
flush(messageType?: MessageType): void;
|
|
21
|
+
getErrorMessages(): string[];
|
|
21
22
|
parseLine(line: string): void;
|
|
22
23
|
_testStart(name: string): void;
|
|
23
24
|
_testEnd(name: string): void;
|
|
@@ -39,6 +39,9 @@ export class MMF1 {
|
|
|
39
39
|
}
|
|
40
40
|
this.output = [];
|
|
41
41
|
}
|
|
42
|
+
getErrorMessages() {
|
|
43
|
+
return this.output.filter(out => out.type === 'fail').map(out => out.message);
|
|
44
|
+
}
|
|
42
45
|
parseLine(line) {
|
|
43
46
|
if (line.startsWith('mops:1:start ')) {
|
|
44
47
|
this._testStart(line.split('mops:1:start ')[1] || '');
|
|
@@ -2,13 +2,18 @@ import { MMF1 } from '../mmf1.js';
|
|
|
2
2
|
import { Reporter } from './reporter.js';
|
|
3
3
|
import { TestMode } from '../../../types.js';
|
|
4
4
|
export declare class SilentReporter implements Reporter {
|
|
5
|
+
total: number;
|
|
5
6
|
passed: number;
|
|
6
7
|
failed: number;
|
|
7
8
|
skipped: number;
|
|
8
9
|
passedFiles: number;
|
|
9
10
|
failedFiles: number;
|
|
10
11
|
passedNamesFlat: string[];
|
|
11
|
-
|
|
12
|
+
flushOnError: boolean;
|
|
13
|
+
errorOutput: string;
|
|
14
|
+
onProgress: () => void;
|
|
15
|
+
constructor(flushOnError?: boolean, onProgress?: () => void);
|
|
16
|
+
addFiles(files: string[]): void;
|
|
12
17
|
addRun(file: string, mmf: MMF1, state: Promise<void>, _mode: TestMode): void;
|
|
13
18
|
done(): boolean;
|
|
14
19
|
}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { absToRel } from '../utils.js';
|
|
3
3
|
export class SilentReporter {
|
|
4
|
-
constructor() {
|
|
4
|
+
constructor(flushOnError = true, onProgress = () => { }) {
|
|
5
|
+
this.total = 0;
|
|
5
6
|
this.passed = 0;
|
|
6
7
|
this.failed = 0;
|
|
7
8
|
this.skipped = 0;
|
|
8
9
|
this.passedFiles = 0;
|
|
9
10
|
this.failedFiles = 0;
|
|
10
11
|
this.passedNamesFlat = [];
|
|
12
|
+
this.flushOnError = true;
|
|
13
|
+
this.errorOutput = '';
|
|
14
|
+
this.onProgress = () => { };
|
|
15
|
+
this.flushOnError = flushOnError;
|
|
16
|
+
this.onProgress = onProgress;
|
|
17
|
+
}
|
|
18
|
+
addFiles(files) {
|
|
19
|
+
this.total = files.length;
|
|
11
20
|
}
|
|
12
|
-
addFiles(_files) { }
|
|
13
21
|
addRun(file, mmf, state, _mode) {
|
|
14
22
|
state.then(() => {
|
|
15
23
|
this.passed += mmf.passed;
|
|
@@ -23,10 +31,15 @@ export class SilentReporter {
|
|
|
23
31
|
this.passedFiles += Number(mmf.failed === 0);
|
|
24
32
|
this.failedFiles += Number(mmf.failed !== 0);
|
|
25
33
|
if (mmf.failed) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
let output = `${chalk.red('✖')} ${absToRel(file)}\n${mmf.getErrorMessages().join('\n')}\n${'-'.repeat(50)}`;
|
|
35
|
+
if (this.flushOnError) {
|
|
36
|
+
console.log(output);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.errorOutput = `${this.errorOutput}\n${output}`.trim();
|
|
40
|
+
}
|
|
29
41
|
}
|
|
42
|
+
this.onProgress();
|
|
30
43
|
});
|
|
31
44
|
}
|
|
32
45
|
done() {
|
|
@@ -9,5 +9,5 @@ type TestOptions = {
|
|
|
9
9
|
replica: ReplicaName;
|
|
10
10
|
};
|
|
11
11
|
export declare function test(filter?: string, options?: Partial<TestOptions>): Promise<void>;
|
|
12
|
-
export declare function testWithReporter(reporterName: ReporterName | Reporter | undefined, filter: string | undefined, defaultMode: TestMode | undefined, replicaType: ReplicaName, watch?: boolean): Promise<boolean>;
|
|
12
|
+
export declare function testWithReporter(reporterName: ReporterName | Reporter | undefined, filter: string | undefined, defaultMode: TestMode | undefined, replicaType: ReplicaName, watch?: boolean, signal?: AbortSignal): Promise<boolean>;
|
|
13
13
|
export {};
|
|
@@ -59,13 +59,22 @@ export async function test(filter = '', options = {}) {
|
|
|
59
59
|
process.exit(0);
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
+
else {
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
62
65
|
});
|
|
63
66
|
// todo: run only changed for *.test.mo?
|
|
64
67
|
// todo: run all for *.mo?
|
|
68
|
+
let curRun = Promise.resolve(true);
|
|
69
|
+
let controller = new AbortController();
|
|
65
70
|
let run = debounce(async () => {
|
|
71
|
+
controller.abort();
|
|
72
|
+
await curRun;
|
|
66
73
|
console.clear();
|
|
67
74
|
process.stdout.write('\x1Bc');
|
|
68
|
-
|
|
75
|
+
controller = new AbortController();
|
|
76
|
+
curRun = runAll(options.reporter, filter, options.mode, replicaType, true, controller.signal);
|
|
77
|
+
await curRun;
|
|
69
78
|
console.log('-'.repeat(50));
|
|
70
79
|
console.log('Waiting for file changes...');
|
|
71
80
|
console.log(chalk.gray((`Press ${chalk.gray('Ctrl+C')} to exit.`)));
|
|
@@ -91,11 +100,11 @@ export async function test(filter = '', options = {}) {
|
|
|
91
100
|
}
|
|
92
101
|
let mocPath = '';
|
|
93
102
|
let wasmtimePath = '';
|
|
94
|
-
async function runAll(reporterName, filter = '', mode = 'interpreter', replicaType, watch = false) {
|
|
95
|
-
let done = await testWithReporter(reporterName, filter, mode, replicaType, watch);
|
|
103
|
+
async function runAll(reporterName, filter = '', mode = 'interpreter', replicaType, watch = false, signal) {
|
|
104
|
+
let done = await testWithReporter(reporterName, filter, mode, replicaType, watch, signal);
|
|
96
105
|
return done;
|
|
97
106
|
}
|
|
98
|
-
export async function testWithReporter(reporterName, filter = '', defaultMode = 'interpreter', replicaType, watch = false) {
|
|
107
|
+
export async function testWithReporter(reporterName, filter = '', defaultMode = 'interpreter', replicaType, watch = false, signal) {
|
|
99
108
|
let rootDir = getRootDir();
|
|
100
109
|
let files = [];
|
|
101
110
|
let libFiles = globSync('**/test?(s)/lib.mo', globConfig);
|
|
@@ -149,6 +158,9 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
149
158
|
replica.dir = testTempDir;
|
|
150
159
|
fs.mkdirSync(testTempDir, { recursive: true });
|
|
151
160
|
await parallel(os.cpus().length, files, async (file) => {
|
|
161
|
+
if (signal?.aborted) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
152
164
|
let mmf = new MMF1('store', absToRel(file));
|
|
153
165
|
// mode overrides
|
|
154
166
|
let lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
@@ -175,14 +187,26 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
175
187
|
let mocArgs = ['--hide-warnings', '--error-detail=2', ...sourcesArr.join(' ').split(' '), file].filter(x => x);
|
|
176
188
|
// interpret
|
|
177
189
|
if (mode === 'interpreter') {
|
|
178
|
-
let proc = spawn(mocPath, ['-r', '-ref-system-api', ...mocArgs]);
|
|
190
|
+
let proc = spawn(mocPath, ['-r', '-ref-system-api', ...mocArgs], { signal });
|
|
191
|
+
proc.addListener('error', (error) => {
|
|
192
|
+
if (error?.code === 'ABORT_ERR') {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
throw error;
|
|
196
|
+
});
|
|
179
197
|
pipeMMF(proc, mmf).then(resolve);
|
|
180
198
|
}
|
|
181
199
|
// build and run wasm
|
|
182
200
|
else if (mode === 'wasi') {
|
|
183
201
|
let wasmFile = `${path.join(testTempDir, path.parse(file).name)}.wasm`;
|
|
184
202
|
// build
|
|
185
|
-
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, '-wasi-system-api', ...mocArgs]);
|
|
203
|
+
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, '-wasi-system-api', ...mocArgs], { signal });
|
|
204
|
+
buildProc.addListener('error', (error) => {
|
|
205
|
+
if (error?.code === 'ABORT_ERR') {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
throw error;
|
|
209
|
+
});
|
|
186
210
|
pipeMMF(buildProc, mmf).then(async () => {
|
|
187
211
|
if (mmf.failed > 0) {
|
|
188
212
|
return;
|
|
@@ -204,7 +228,13 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
204
228
|
console.error(chalk.red('Minimum wasmtime version is 14.0.0. Please update wasmtime to the latest version'));
|
|
205
229
|
process.exit(1);
|
|
206
230
|
}
|
|
207
|
-
let proc = spawn(wasmtimePath, wasmtimeArgs);
|
|
231
|
+
let proc = spawn(wasmtimePath, wasmtimeArgs, { signal });
|
|
232
|
+
proc.addListener('error', (error) => {
|
|
233
|
+
if (error?.code === 'ABORT_ERR') {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
throw error;
|
|
237
|
+
});
|
|
208
238
|
await pipeMMF(proc, mmf);
|
|
209
239
|
}).finally(() => {
|
|
210
240
|
fs.rmSync(wasmFile, { force: true });
|
|
@@ -215,18 +245,30 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
215
245
|
// mmf.strategy = 'print'; // because we run replica tests one-by-one
|
|
216
246
|
let wasmFile = `${path.join(testTempDir, path.parse(file).name)}.wasm`;
|
|
217
247
|
// build
|
|
218
|
-
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, ...mocArgs]);
|
|
248
|
+
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, ...mocArgs], { signal });
|
|
249
|
+
buildProc.addListener('error', (error) => {
|
|
250
|
+
if (error?.code === 'ABORT_ERR') {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
throw error;
|
|
254
|
+
});
|
|
219
255
|
pipeMMF(buildProc, mmf).then(async () => {
|
|
220
256
|
if (mmf.failed > 0) {
|
|
221
257
|
return;
|
|
222
258
|
}
|
|
223
259
|
await startReplicaOnce(replica, replicaType);
|
|
260
|
+
if (signal?.aborted) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
224
263
|
let canisterName = path.parse(file).name;
|
|
225
264
|
let idlFactory = ({ IDL }) => {
|
|
226
265
|
return IDL.Service({ 'runTests': IDL.Func([], [], []) });
|
|
227
266
|
};
|
|
228
|
-
let
|
|
229
|
-
|
|
267
|
+
let canister = await replica.deploy(canisterName, wasmFile, idlFactory, undefined, signal);
|
|
268
|
+
if (signal?.aborted || !canister) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
pipeStdoutToMMF(canister.stream, mmf);
|
|
230
272
|
let actor = await replica.getActor(canisterName);
|
|
231
273
|
try {
|
|
232
274
|
if (globalThis.mopsReplicaTestRunning) {
|
|
@@ -239,6 +281,9 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
239
281
|
}, Math.random() * 1000 | 0);
|
|
240
282
|
});
|
|
241
283
|
}
|
|
284
|
+
if (signal?.aborted) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
242
287
|
globalThis.mopsReplicaTestRunning = true;
|
|
243
288
|
await actor.runTests();
|
|
244
289
|
globalThis.mopsReplicaTestRunning = false;
|
|
@@ -250,10 +295,14 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
250
295
|
stderrStream.write(e.message);
|
|
251
296
|
}
|
|
252
297
|
}).finally(async () => {
|
|
298
|
+
globalThis.mopsReplicaTestRunning = false;
|
|
253
299
|
fs.rmSync(wasmFile, { force: true });
|
|
254
300
|
}).then(resolve);
|
|
255
301
|
}
|
|
256
302
|
});
|
|
303
|
+
if (signal?.aborted) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
257
306
|
reporter.addRun(file, mmf, promise, mode);
|
|
258
307
|
await promise;
|
|
259
308
|
});
|
|
@@ -261,6 +310,9 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
261
310
|
await replica.stop();
|
|
262
311
|
fs.rmSync(testTempDir, { recursive: true, force: true });
|
|
263
312
|
}
|
|
313
|
+
if (signal?.aborted) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
264
316
|
return reporter.done();
|
|
265
317
|
}
|
|
266
318
|
function pipeStdoutToMMF(stdout, mmf) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ErrorChecker } from './error-checker.js';
|
|
2
|
+
import { Generator } from './generator.js';
|
|
3
|
+
export declare class Deployer {
|
|
4
|
+
verbose: boolean;
|
|
5
|
+
canisters: Record<string, string>;
|
|
6
|
+
status: 'pending' | 'running' | 'syntax-error' | 'dfx-error' | 'error' | 'success';
|
|
7
|
+
errorChecker: ErrorChecker;
|
|
8
|
+
generator: Generator;
|
|
9
|
+
success: number;
|
|
10
|
+
errors: string[];
|
|
11
|
+
aborted: boolean;
|
|
12
|
+
controllers: Map<string, AbortController>;
|
|
13
|
+
currentRun: Promise<any> | undefined;
|
|
14
|
+
constructor({ verbose, canisters, errorChecker, generator }: {
|
|
15
|
+
verbose: boolean;
|
|
16
|
+
canisters: Record<string, string>;
|
|
17
|
+
errorChecker: ErrorChecker;
|
|
18
|
+
generator: Generator;
|
|
19
|
+
});
|
|
20
|
+
reset(): void;
|
|
21
|
+
abortCurrent(): Promise<void>;
|
|
22
|
+
run(onProgress: () => void): Promise<void>;
|
|
23
|
+
getOutput(): string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { execFile, execSync } from 'node:child_process';
|
|
5
|
+
import { parallel } from '../../parallel.js';
|
|
6
|
+
import { getRootDir } from '../../mops.js';
|
|
7
|
+
export class Deployer {
|
|
8
|
+
constructor({ verbose, canisters, errorChecker, generator }) {
|
|
9
|
+
this.verbose = false;
|
|
10
|
+
this.canisters = {};
|
|
11
|
+
this.status = 'pending';
|
|
12
|
+
this.success = 0;
|
|
13
|
+
this.errors = [];
|
|
14
|
+
this.aborted = false;
|
|
15
|
+
this.controllers = new Map();
|
|
16
|
+
this.verbose = verbose;
|
|
17
|
+
this.canisters = canisters;
|
|
18
|
+
this.errorChecker = errorChecker;
|
|
19
|
+
this.generator = generator;
|
|
20
|
+
}
|
|
21
|
+
reset() {
|
|
22
|
+
this.status = 'pending';
|
|
23
|
+
this.success = 0;
|
|
24
|
+
this.errors = [];
|
|
25
|
+
}
|
|
26
|
+
async abortCurrent() {
|
|
27
|
+
this.aborted = true;
|
|
28
|
+
for (let controller of this.controllers.values()) {
|
|
29
|
+
controller.abort();
|
|
30
|
+
}
|
|
31
|
+
this.controllers.clear();
|
|
32
|
+
await this.currentRun;
|
|
33
|
+
this.reset();
|
|
34
|
+
this.aborted = false;
|
|
35
|
+
}
|
|
36
|
+
async run(onProgress) {
|
|
37
|
+
await this.abortCurrent();
|
|
38
|
+
if (this.errorChecker.status === 'error') {
|
|
39
|
+
this.status = 'syntax-error';
|
|
40
|
+
onProgress();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (Object.keys(this.canisters).length === 0) {
|
|
44
|
+
this.status = 'success';
|
|
45
|
+
onProgress();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let rootDir = getRootDir();
|
|
49
|
+
try {
|
|
50
|
+
execSync('dfx ping', { cwd: rootDir });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.status = 'dfx-error';
|
|
54
|
+
onProgress();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.status = 'running';
|
|
58
|
+
onProgress();
|
|
59
|
+
// create canisters (sequentially to avoid DFX errors)
|
|
60
|
+
let resolve;
|
|
61
|
+
this.currentRun = new Promise((res) => resolve = res);
|
|
62
|
+
for (let canister of Object.keys(this.canisters)) {
|
|
63
|
+
let controller = new AbortController();
|
|
64
|
+
let { signal } = controller;
|
|
65
|
+
this.controllers.set(canister, controller);
|
|
66
|
+
await promisify(execFile)('dfx', ['canister', 'create', canister], { cwd: rootDir, signal }).catch((error) => {
|
|
67
|
+
if (error.code === 'ABORT_ERR') {
|
|
68
|
+
return { stderr: '' };
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
});
|
|
72
|
+
this.controllers.delete(canister);
|
|
73
|
+
}
|
|
74
|
+
resolve?.();
|
|
75
|
+
if (this.aborted) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this.currentRun = parallel(os.cpus().length, [...Object.keys(this.canisters)], async (canister) => {
|
|
79
|
+
let controller = new AbortController();
|
|
80
|
+
let { signal } = controller;
|
|
81
|
+
this.controllers.set(canister, controller);
|
|
82
|
+
// build
|
|
83
|
+
if (this.generator.status !== 'success' || !this.generator.canisters[canister]) {
|
|
84
|
+
await promisify(execFile)('dfx', ['build', canister], { cwd: rootDir, signal }).catch((error) => {
|
|
85
|
+
if (error.code === 'ABORT_ERR') {
|
|
86
|
+
return { stderr: '' };
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// install
|
|
92
|
+
await promisify(execFile)('dfx', ['canister', 'install', '--mode=auto', canister], { cwd: rootDir, signal }).catch((error) => {
|
|
93
|
+
if (error.code === 'ABORT_ERR') {
|
|
94
|
+
return { stderr: '' };
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
});
|
|
98
|
+
this.success += 1;
|
|
99
|
+
this.controllers.delete(canister);
|
|
100
|
+
onProgress();
|
|
101
|
+
});
|
|
102
|
+
await this.currentRun;
|
|
103
|
+
if (!this.aborted) {
|
|
104
|
+
this.status = 'success';
|
|
105
|
+
}
|
|
106
|
+
onProgress();
|
|
107
|
+
}
|
|
108
|
+
getOutput() {
|
|
109
|
+
let get = (v) => v.toString();
|
|
110
|
+
let count = (this.status === 'running' ? get : chalk.bold[this.errors.length > 0 ? 'redBright' : 'green'])(this.errors.length || this.success);
|
|
111
|
+
if (this.status === 'pending') {
|
|
112
|
+
return `Deploy: ${chalk.gray('(pending)')}`;
|
|
113
|
+
}
|
|
114
|
+
if (this.status === 'running') {
|
|
115
|
+
return `Deploy: ${count}/${Object.keys(this.canisters).length} ${chalk.gray('(running)')}`;
|
|
116
|
+
}
|
|
117
|
+
if (this.status === 'syntax-error') {
|
|
118
|
+
return `Deploy: ${chalk.gray('(errors)')}`;
|
|
119
|
+
}
|
|
120
|
+
if (this.status === 'dfx-error') {
|
|
121
|
+
return `Deploy: ${chalk.gray('(dfx not running)')}`;
|
|
122
|
+
}
|
|
123
|
+
return `Deploy: ${count}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class ErrorChecker {
|
|
2
|
+
verbose: boolean;
|
|
3
|
+
canisters: Record<string, string>;
|
|
4
|
+
status: 'pending' | 'running' | 'error' | 'success';
|
|
5
|
+
errors: string[];
|
|
6
|
+
constructor({ verbose, canisters }: {
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
canisters: Record<string, string>;
|
|
9
|
+
});
|
|
10
|
+
reset(): void;
|
|
11
|
+
run(onProgress: () => void): Promise<void>;
|
|
12
|
+
getOutput(): string;
|
|
13
|
+
}
|