Skip to content

Commit

Permalink
Merge pull request #259 from ZenVoich/dfx-pocket-ic
Browse files Browse the repository at this point in the history
pocket-ic from dfx
  • Loading branch information
ZenVoich authored Dec 10, 2024
2 parents b7df34a + ef68713 commit 6d011c1
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 13 deletions.
2 changes: 2 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
- Removed `mops transfer-ownership` command
- Added `mops owner` command to manage package owners ([docs](https://docs.mops.one/cli/mops-owner))
- Added `mops maintainers` command to manage package maintainers ([docs](https://docs.mops.one/cli/mops-maintainers))
- Added experimental support for pocket-ic replica that comes with dfx in `mops test` command ([docs](https://docs.mops.one/cli/mops-test#--replica))
- Added flag `--verbose` to `mops test` command to show replica logs
- Fixed bug where `mops watch` would fail if dfx.json did not exist

## 1.1.2
Expand Down
1 change: 1 addition & 0 deletions cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ program
.addOption(new Option('--mode <mode>', 'Test mode').choices(['interpreter', 'wasi', 'replica']).default('interpreter'))
.addOption(new Option('--replica <replica>', 'Which replica to use to run tests in replica mode').choices(['dfx', 'pocket-ic']))
.option('-w, --watch', 'Enable watch mode')
.option('--verbose', 'Verbose output')
.action(async (filter, options) => {
checkConfigFile(true);
await installAll({silent: true, lock: 'ignore', installFromLockFile: true});
Expand Down
47 changes: 38 additions & 9 deletions cli/commands/replica.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ import {spawn as spawnAsync} from 'promisify-child-process';
import {IDL} from '@dfinity/candid';
import {Actor, HttpAgent} from '@dfinity/agent';
import {PocketIc, PocketIcServer} from 'pic-ic';
import chalk from 'chalk';

import {readConfig} from '../mops.js';
import {toolchain} from './toolchain/index.js';
import {getDfxVersion} from '../helpers/get-dfx-version.js';

type StartOptions = {
type ?: 'dfx' | 'pocket-ic';
type ?: 'dfx' | 'pocket-ic' | 'dfx-pocket-ic';
dir ?: string;
verbose ?: boolean;
silent ?: boolean;
};

export class Replica {
type : 'dfx' | 'pocket-ic' = 'dfx';
type : 'dfx' | 'pocket-ic' | 'dfx-pocket-ic' = 'dfx';
verbose = false;
canisters : Record<string, {cwd : string; canisterId : string; actor : any; stream : PassThrough;}> = {};
pocketIcServer ?: PocketIcServer;
Expand All @@ -36,20 +38,33 @@ export class Replica {

silent || console.log(`Starting ${this.type} replica...`);

if (this.type == 'dfx') {
if (this.type === 'dfx' || this.type === 'dfx-pocket-ic') {
fs.mkdirSync(this.dir, {recursive: true});
fs.writeFileSync(path.join(this.dir, 'dfx.json'), JSON.stringify(this.dfxJson(''), null, 2));
fs.writeFileSync(path.join(this.dir, 'canister.did'), 'service : { runTests: () -> (); }');

await this.stop();

this.dfxProcess = spawn('dfx', ['start', '--clean', '--artificial-delay', '0', (this.verbose ? '' : '-qqqq')].filter(x => x), {cwd: this.dir});
this.dfxProcess = spawn('dfx', ['start', this.type === 'dfx-pocket-ic' ? '--pocketic' : '', '--clean', (this.verbose ? '' : '-qqqq'), '--artificial-delay', '0'].filter(x => x).flat(), {cwd: this.dir});

// process canister logs
this._attachCanisterLogHandler(this.dfxProcess);

this.dfxProcess.stdout.on('data', (data) => {
console.log('DFX:', data.toString());
if (this.verbose) {
console.log('DFX:', data.toString());
}
});

this.dfxProcess.stderr.on('data', (data) => {
if (this.verbose) {
console.error('DFX:', data.toString());
}
if (data.toString().includes('Failed to bind socket to')) {
console.error(chalk.red(data.toString()));
console.log('Please try again after some time');
process.exit(11);
}
});

// await for dfx to start
Expand Down Expand Up @@ -115,9 +130,22 @@ export class Replica {
}

async stop(sigint = false) {
if (this.type == 'dfx') {
this.dfxProcess?.kill();
// execSync('dfx stop' + (this.verbose ? '' : ' -qqqq'), {cwd: this.dir, timeout: 10_000, stdio: ['pipe', this.verbose ? 'inherit' : 'ignore', 'pipe']});
if (this.type === 'dfx' || this.type === 'dfx-pocket-ic') {
if (this.dfxProcess) {
this.dfxProcess.kill();
// give replica some time to stop
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}

// if (!this.dfxProcess) {
// try {
// execSync('dfx killall', {cwd: this.dir, timeout: 3_000, stdio: ['pipe', this.verbose ? 'inherit' : 'ignore', 'pipe']});
// execSync('dfx stop' + (this.verbose ? '' : ' -qqqq'), {cwd: this.dir, timeout: 10_000, stdio: ['pipe', this.verbose ? 'inherit' : 'ignore', 'pipe']});
// }
// catch {}
// }
}
else if (this.pocketIc && this.pocketIcServer) {
if (!sigint) {
Expand All @@ -128,7 +156,7 @@ export class Replica {
}

async deploy(name : string, wasm : string, idlFactory : IDL.InterfaceFactory, cwd : string = process.cwd(), signal ?: AbortSignal) {
if (this.type === 'dfx') {
if (this.type === 'dfx' || this.type === 'dfx-pocket-ic') {
// prepare dfx.json for current canister
let dfxJson = path.join(this.dir, 'dfx.json');

Expand Down Expand Up @@ -253,6 +281,7 @@ export class Replica {
return {
version: 1,
canisters,
dfx: getDfxVersion(),
defaults: {
build: {
packtool: 'mops sources',
Expand Down
19 changes: 18 additions & 1 deletion cli/commands/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import chalk from 'chalk';
import {globSync} from 'glob';
import chokidar from 'chokidar';
import debounce from 'debounce';
import {SemVer} from 'semver';

import {sources} from '../sources.js';
import {getRootDir, readConfig} from '../../mops.js';
Expand All @@ -25,6 +26,7 @@ import {Replica} from '../replica.js';
import {ActorMethod} from '@dfinity/agent';
import {PassThrough, Readable} from 'node:stream';
import {TestMode} from '../../types.js';
import {getDfxVersion} from '../../helpers/get-dfx-version.js';

let ignore = [
'**/node_modules/**',
Expand All @@ -39,13 +41,14 @@ let globConfig = {
};

type ReporterName = 'verbose' | 'files' | 'compact' | 'silent';
type ReplicaName = 'dfx' | 'pocket-ic';
type ReplicaName = 'dfx' | 'pocket-ic' | 'dfx-pocket-ic';

type TestOptions = {
watch : boolean;
reporter : ReporterName;
mode : TestMode;
replica : ReplicaName;
verbose : boolean;
};


Expand All @@ -66,7 +69,20 @@ export async function test(filter = '', options : Partial<TestOptions> = {}) {
let rootDir = getRootDir();

let replicaType = options.replica ?? (config.toolchain?.['pocket-ic'] ? 'pocket-ic' : 'dfx' as ReplicaName);

if (replicaType === 'pocket-ic' && !config.toolchain?.['pocket-ic']) {
let dfxVersion = getDfxVersion();
if (!dfxVersion || new SemVer(dfxVersion).compare('0.24.1') < 0) {
console.log(chalk.red('Please update dfx to the version >=0.24.1 or specify pocket-ic version in mops.toml'));
process.exit(1);
}
else {
replicaType = 'dfx-pocket-ic';
}
}

replica.type = replicaType;
replica.verbose = !!options.verbose;

if (options.watch) {
replica.ttl = 60 * 15; // 15 minutes
Expand Down Expand Up @@ -202,6 +218,7 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
let testTempDir = path.join(getRootDir(), '.mops/.test/');
replica.dir = testTempDir;

fs.rmSync(testTempDir, {recursive: true, force: true});
fs.mkdirSync(testTempDir, {recursive: true});

await parallel(os.cpus().length, files, async (file : string) => {
Expand Down
2 changes: 1 addition & 1 deletion dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"packtool": "mops sources"
}
},
"dfx": "0.24.2",
"dfx": "0.24.3",
"networks": {
"staging": {
"type": "persistent",
Expand Down
16 changes: 14 additions & 2 deletions docs/docs/cli/4-dev/01-mops-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ Test reporter.

Available reporters:

- `verbose` - print each test name
- `verbose` - print each file/suite/test name and `Debug.print` output
- `files` - print only test files
- `compact` - pretty progress bar
- `silent` - print only errors

Default `verbose` if there is only one file to test and `files` otherwise.

:::note
Only `verbose` reporter prints `Debug.print` output.
:::

### `--watch`, `-w`

Re-run tests every time you change *.mo files.
Expand Down Expand Up @@ -93,4 +97,12 @@ Default `pocket-ic` if `pocket-ic` is specified in `mops.toml` in `[toolchain]`

Possible values:
- `dfx` - use `dfx` local replica
- `pocket-ic` - use [PocketIC](https://pypi.org/project/pocket-ic/) light replica via [pic.js](https://www.npmjs.com/package/@hadronous/pic) wrapper
- `pocket-ic` - use [PocketIC](https://pypi.org/project/pocket-ic/) light replica via [pic.js](https://www.npmjs.com/package/@hadronous/pic) wrapper

:::info
If you run `mops test --replica pocket-ic` AND `pocket-ic` is not specified in `mops.toml` in `[toolchain]` section, Mops will use pocket-ic replica that comes with dfx (`dfx start --pocketic`).
:::

### `--verbose`

Show replica logs

0 comments on commit 6d011c1

Please sign in to comment.