From 2a4426625f7d5a7f0422ec10dd1e374501a074f9 Mon Sep 17 00:00:00 2001 From: ZenVoich Date: Tue, 22 Aug 2023 13:43:42 +0400 Subject: [PATCH 1/5] [cli] package test stats WIP --- cli/cli.ts | 11 ++-- cli/commands/publish.ts | 57 ++++++++++++------- cli/commands/test/mmf1.ts | 11 +++- .../test/reporters/compact-reporter.ts | 4 ++ .../test/reporters/silent-reporter.ts | 42 ++++++++++++++ .../test/reporters/verbose-reporter.ts | 4 ++ cli/commands/test/test.ts | 25 +++++--- test/.hello/test/lib.test.mo | 4 ++ test/.hello/test/suite.test.mo | 12 ++++ test/.hello/test/test.mo | 17 ++++++ 10 files changed, 151 insertions(+), 36 deletions(-) create mode 100644 cli/commands/test/reporters/silent-reporter.ts create mode 100644 test/.hello/test/lib.test.mo create mode 100644 test/.hello/test/suite.test.mo create mode 100644 test/.hello/test/test.mo diff --git a/cli/cli.ts b/cli/cli.ts index 08db56e0..426bf6a6 100644 --- a/cli/cli.ts +++ b/cli/cli.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node import fs from 'node:fs'; -import {program, Argument} from 'commander'; +import {program, Argument, Option} from 'commander'; import chalk from 'chalk'; import {Principal} from '@dfinity/principal'; @@ -100,6 +100,7 @@ program .command('publish') .description('Publish package to the mops registry') .option('--no-docs', 'Do not generate docs') + .option('--no-test', 'Do not run tests') .action(async (options) => { if (!checkConfigFile()) { process.exit(1); @@ -188,14 +189,10 @@ program program .command('test [filter]') .description('Run tests') - .option('-r, --reporter ', 'Choose reporter: verbose, compact, files') + .addOption(new Option('-r, --reporter ', 'Test reporter').choices(['verbose', 'compact', 'files', 'silent']).default('verbose')) + .addOption(new Option('--mode ', 'Test mode').choices(['interpreter', 'wasi']).default('interpreter')) .option('-w, --watch', 'Enable watch mode') - .option('--mode ', 'Test mode: \'interpreter\' or \'wasi\' (default \'interpreter\'') .action(async (filter, options) => { - if (options.mode && !['interpreter', 'wasi'].includes(options.mode)) { - console.log(`Unknown --mode value '${options.mode}'. Allowed: interpreter, wasi`); - process.exit(1); - } await test(filter, options); }); diff --git a/cli/commands/publish.ts b/cli/commands/publish.ts index 3c54bfd1..051de543 100644 --- a/cli/commands/publish.ts +++ b/cli/commands/publish.ts @@ -10,8 +10,10 @@ import {parallel} from '../parallel.js'; import {docs} from './docs.js'; import {DependencyV2, PackageConfigV2} from '../declarations/main/main.did.js'; import {Dependency} from '../types.js'; +import {testWithReporter} from './test/test.js'; +import {SilentReporter} from './test/reporters/silent-reporter.js'; -export async function publish({noDocs = false} = {}) { +export async function publish(options: {docs?: boolean, test?: boolean} = {}) { if (!checkConfigFile()) { return; } @@ -19,24 +21,26 @@ export async function publish({noDocs = false} = {}) { let rootDir = getRootDir(); let config = readConfig(); + console.log(`Publishing ${config.package?.name}@${config.package?.version}`); + // validate for (let key of Object.keys(config)) { if (!['package', 'dependencies', 'dev-dependencies', 'scripts'].includes(key)) { console.log(chalk.red('Error: ') + `Unknown config section [${key}]`); - return; + process.exit(1); } } // required fields if (!config.package) { console.log(chalk.red('Error: ') + 'Please specify [package] section in your mops.toml'); - return; + process.exit(1); } for (let key of ['name', 'version']) { // @ts-ignore if (!config.package[key]) { console.log(chalk.red('Error: ') + `Please specify "${key}" in [config] section in your mops.toml`); - return; + process.exit(1); } } @@ -74,7 +78,7 @@ export async function publish({noDocs = false} = {}) { for (let key of Object.keys(config.package)) { if (!packageKeys.includes(key)) { console.log(chalk.red('Error: ') + `Unknown config key 'package.${key}'`); - return; + process.exit(1); } } @@ -100,20 +104,20 @@ export async function publish({noDocs = false} = {}) { // @ts-ignore if (config.package[key] && config.package[key].length > max) { console.log(chalk.red('Error: ') + `package.${key} value max length is ${max}`); - return; + process.exit(1); } } if (config.dependencies) { if (Object.keys(config.dependencies).length > 100) { console.log(chalk.red('Error: ') + 'max dependencies is 100'); - return; + process.exit(1); } for (let dep of Object.values(config.dependencies)) { if (dep.path) { console.log(chalk.red('Error: ') + 'you can\'t publish packages with local dependencies'); - return; + process.exit(1); } delete dep.path; } @@ -122,13 +126,13 @@ export async function publish({noDocs = false} = {}) { if (config['dev-dependencies']) { if (Object.keys(config['dev-dependencies']).length > 100) { console.log(chalk.red('Error: ') + 'max dev-dependencies is 100'); - return; + process.exit(1); } for (let dep of Object.values(config['dev-dependencies'])) { if (dep.path) { console.log(chalk.red('Error: ') + 'you can\'t publish packages with local dev-dependencies'); - return; + process.exit(1); } delete dep.path; } @@ -197,7 +201,8 @@ export async function publish({noDocs = false} = {}) { // generate docs let docsFile = path.join(rootDir, '.mops/.docs/docs.tgz'); - if (!noDocs) { + if (options.docs) { + console.log('Generating documentation...'); await docs({silent: true}); if (fs.existsSync(docsFile)) { files.unshift(docsFile); @@ -207,27 +212,39 @@ export async function publish({noDocs = false} = {}) { // check required files if (!files.includes('mops.toml')) { console.log(chalk.red('Error: ') + ' please add mops.toml file'); - return; + process.exit(1); } if (!files.includes('README.md')) { console.log(chalk.red('Error: ') + ' please add README.md file'); - return; + process.exit(1); } // check allowed exts for (let file of files) { if (!minimatch(file, '**/*.{mo,did,md,toml}') && !file.toLowerCase().endsWith('license') && !file.toLowerCase().endsWith('notice') && file !== docsFile) { console.log(chalk.red('Error: ') + `file ${file} has unsupported extension. Allowed: .mo, .did, .md, .toml`); - return; + process.exit(1); } } + // test + if (options.test) { + console.log('Running tests...'); + let reporter = new SilentReporter; + await testWithReporter(reporter); + if (reporter.failed > 0) { + console.log(chalk.red('Error: ') + 'tests failed'); + process.exit(1); + } + // console.log(reporter.passedNamesFlat); + } + // progress let total = files.length + 2; let step = 0; function progress() { step++; - logUpdate(`Publishing ${config.package?.name}@${config.package?.version} ${progressBar(step, total)}`); + logUpdate(`Uploading files ${progressBar(step, total)}`); } // upload config @@ -277,11 +294,11 @@ export async function publish({noDocs = false} = {}) { // finish progress(); - let res = await actor.finishPublish(puiblishingId); - if ('err' in res) { - console.log(chalk.red('Error: ') + res.err); - return; - } + // let res = await actor.finishPublish(puiblishingId); + // if ('err' in res) { + // console.log(chalk.red('Error: ') + res.err); + // return; + // } console.log(chalk.green('Published ') + `${config.package.name}@${config.package.version}`); } \ No newline at end of file diff --git a/cli/commands/test/mmf1.ts b/cli/commands/test/mmf1.ts index 45738a56..02f59030 100644 --- a/cli/commands/test/mmf1.ts +++ b/cli/commands/test/mmf1.ts @@ -9,6 +9,7 @@ type TestStatus = 'pass' | 'fail' | 'skip'; type MessageType = 'pass' | 'fail' | 'skip' | 'suite' | 'stdout'; export class MMF1 { + file: string; stack: string[] = []; currSuite: string = ''; failed = 0; @@ -19,9 +20,16 @@ export class MMF1 { type: MessageType; message: string; }[] = []; + nestingSymbol = ' › '; + // or + // or + // or + // or ... + passedNamesFlat: string[] = []; - constructor(srategy: Strategy) { + constructor(srategy: Strategy, file: string) { this.srategy = srategy; + this.file = file; } _log(type: MessageType, ...args: string[]) { @@ -90,6 +98,7 @@ export class MMF1 { } this.passed++; this._log(status, ' '.repeat(this.stack.length * 2), chalk.green('✓'), name); + this.passedNamesFlat.push([this.file, ...this.stack, name].join(this.nestingSymbol)); } else if (status === 'fail') { this.failed++; diff --git a/cli/commands/test/reporters/compact-reporter.ts b/cli/commands/test/reporters/compact-reporter.ts index 38c41046..b3c7c748 100644 --- a/cli/commands/test/reporters/compact-reporter.ts +++ b/cli/commands/test/reporters/compact-reporter.ts @@ -32,6 +32,10 @@ export class CompactReporter implements Reporter { this.failed += mmf.failed; this.skipped += mmf.skipped; + if (mmf.passed === 0 && mmf.failed === 0) { + this.passed++; + } + this.passedFiles += Number(mmf.failed === 0); this.failedFiles += Number(mmf.failed !== 0); diff --git a/cli/commands/test/reporters/silent-reporter.ts b/cli/commands/test/reporters/silent-reporter.ts new file mode 100644 index 00000000..4c8be293 --- /dev/null +++ b/cli/commands/test/reporters/silent-reporter.ts @@ -0,0 +1,42 @@ +import chalk from 'chalk'; +import {absToRel} from '../utils.js'; +import {MMF1} from '../mmf1.js'; +import {Reporter} from './reporter.js'; + +export class SilentReporter implements Reporter { + passed = 0; + failed = 0; + skipped = 0; + passedFiles = 0; + failedFiles = 0; + passedNamesFlat: string[] = []; + + addFiles(_files: string[]) {} + + addRun(file: string, mmf: MMF1, state: Promise, _wasiMode: boolean) { + state.then(() => { + this.passed += mmf.passed; + this.failed += mmf.failed; + this.skipped += mmf.skipped; + this.passedNamesFlat = [...this.passedNamesFlat, ...mmf.passedNamesFlat]; + + if (mmf.passed === 0 && mmf.failed === 0) { + this.passed++; + this.passedNamesFlat.push(absToRel(file)); + } + + this.passedFiles += Number(mmf.failed === 0); + this.failedFiles += Number(mmf.failed !== 0); + + if (mmf.failed) { + console.log(chalk.red('✖'), absToRel(file)); + mmf.flush('fail'); + console.log('-'.repeat(50)); + } + }); + } + + done(): boolean { + return this.failed === 0; + } +} \ No newline at end of file diff --git a/cli/commands/test/reporters/verbose-reporter.ts b/cli/commands/test/reporters/verbose-reporter.ts index 35eaf7a5..1ac9286f 100644 --- a/cli/commands/test/reporters/verbose-reporter.ts +++ b/cli/commands/test/reporters/verbose-reporter.ts @@ -25,6 +25,10 @@ export class VerboseReporter implements Reporter { this.failed += mmf.failed; this.skipped += mmf.skipped; + if (mmf.passed === 0 && mmf.failed === 0) { + this.passed++; + } + this.#curFileIndex++ && console.log('-'.repeat(50)); console.log(`Running ${chalk.gray(absToRel(file))} ${wasiMode ? chalk.gray('(wasi)') : ''}`); mmf.flush(); diff --git a/cli/commands/test/test.ts b/cli/commands/test/test.ts index c5fb8f50..078e860e 100644 --- a/cli/commands/test/test.ts +++ b/cli/commands/test/test.ts @@ -13,10 +13,11 @@ import {parallel} from '../../parallel.js'; import {MMF1} from './mmf1.js'; import {absToRel} from './utils.js'; +import {Reporter} from './reporters/reporter.js'; import {VerboseReporter} from './reporters/verbose-reporter.js'; import {FilesReporter} from './reporters/files-reporter.js'; import {CompactReporter} from './reporters/compact-reporter.js'; -import {Reporter} from './reporters/reporter.js'; +import {SilentReporter} from './reporters/silent-reporter.js'; let ignore = [ '**/node_modules/**', @@ -30,9 +31,10 @@ let globConfig = { ignore: ignore, }; +type ReporterName = 'verbose' | 'files' | 'compact' | 'silent'; type TestMode = 'interpreter' | 'wasi'; -export async function test(filter = '', {watch = false, reporter = 'verbose', mode = 'interpreter' as TestMode} = {}) { +export async function test(filter = '', {watch = false, reporter = 'verbose' as ReporterName, mode = 'interpreter' as TestMode} = {}) { let rootDir = getRootDir(); if (watch) { @@ -41,7 +43,7 @@ export async function test(filter = '', {watch = false, reporter = 'verbose', mo let run = debounce(async () => { console.clear(); process.stdout.write('\x1Bc'); - await runAll(filter, reporter); + await runAll(reporter, filter, mode); console.log('-'.repeat(50)); console.log('Waiting for file changes...'); console.log(chalk.gray((`Press ${chalk.gray('Ctrl+C')} to exit.`))); @@ -61,7 +63,7 @@ export async function test(filter = '', {watch = false, reporter = 'verbose', mo run(); } else { - let passed = await runAll(filter, reporter, mode); + let passed = await runAll(reporter, filter, mode); if (!passed) { process.exit(1); } @@ -70,7 +72,7 @@ export async function test(filter = '', {watch = false, reporter = 'verbose', mo let mocPath = process.env.DFX_MOC_PATH; -export async function runAll(filter = '', reporterName = 'verbose', mode: TestMode = 'interpreter') { +export async function runAll(reporterName: ReporterName = 'verbose', filter = '', mode: TestMode = 'interpreter'): Promise { let reporter: Reporter; if (reporterName == 'compact') { reporter = new CompactReporter; @@ -78,10 +80,17 @@ export async function runAll(filter = '', reporterName = 'verbose', mode: TestMo else if (reporterName == 'files') { reporter = new FilesReporter; } + else if (reporterName == 'silent') { + reporter = new SilentReporter; + } else { reporter = new VerboseReporter; } + let done = await testWithReporter(reporter, filter, mode); + return done; +} +export async function testWithReporter(reporter: Reporter, filter = '', mode: TestMode = 'interpreter'): Promise { let rootDir = getRootDir(); let files: string[] = []; let libFiles = globSync('**/test?(s)/lib.mo', globConfig); @@ -98,11 +107,11 @@ export async function runAll(filter = '', reporterName = 'verbose', mode: TestMo if (!files.length) { if (filter) { console.log(`No test files found for filter '${filter}'`); - return; + return false; } console.log('No test files found'); console.log('Put your tests in \'test\' directory in *.test.mo files'); - return; + return false; } reporter.addFiles(files); @@ -117,7 +126,7 @@ export async function runAll(filter = '', reporterName = 'verbose', mode: TestMo fs.mkdirSync(wasmDir, {recursive: true}); await parallel(os.cpus().length, files, async (file: string) => { - let mmf = new MMF1('store'); + let mmf = new MMF1('store', absToRel(file)); let wasiMode = mode === 'wasi' || fs.readFileSync(file, 'utf8').startsWith('// @testmode wasi'); let promise = new Promise((resolve) => { diff --git a/test/.hello/test/lib.test.mo b/test/.hello/test/lib.test.mo new file mode 100644 index 00000000..cca11c5a --- /dev/null +++ b/test/.hello/test/lib.test.mo @@ -0,0 +1,4 @@ +import {hello} "../src"; + +assert hello("World") == "Hello, World!"; +assert hello("Motoko") == "Hello, Motoko!"; \ No newline at end of file diff --git a/test/.hello/test/suite.test.mo b/test/.hello/test/suite.test.mo new file mode 100644 index 00000000..c8bb277d --- /dev/null +++ b/test/.hello/test/suite.test.mo @@ -0,0 +1,12 @@ +import {hello} "../src"; +import {test; suite} "./test"; + +suite("Hello", func() { + test("should say hello to the World", func() { + assert hello("World") == "Hello, World!"; + }); + + test("should say hello to Motoko", func() { + assert hello("Motoko") == "Hello, Motoko!"; + }); +}); \ No newline at end of file diff --git a/test/.hello/test/test.mo b/test/.hello/test/test.mo new file mode 100644 index 00000000..7f1e9075 --- /dev/null +++ b/test/.hello/test/test.mo @@ -0,0 +1,17 @@ +import Prim "mo:prim"; + +module { + public func test(name: Text, fn: () -> ()) { + Prim.debugPrint("mops:1:start " # name); + fn(); + Prim.debugPrint("mops:1:end " # name); + }; + + public func suite(name: Text, fn: () -> ()) { + test(name, fn); + }; + + public func skip(name: Text, fn: () -> ()) { + Prim.debugPrint("mops:1:skip " # name); + }; +}; \ No newline at end of file From 1342d39ba499c40ec087dd04b5370dfbd14b09d6 Mon Sep 17 00:00:00 2001 From: ZenVoich Date: Wed, 23 Aug 2023 12:40:45 +0400 Subject: [PATCH 2/5] [backend] package test stats WIP --- backend/main/main-canister.mo | 63 +++++++++++++++++------- backend/main/types.mo | 6 +++ cli/declarations/main/main.did | 14 ++++++ cli/declarations/main/main.did.d.ts | 9 ++++ cli/declarations/main/main.did.js | 12 +++++ frontend/declarations/main/main.did | 14 ++++++ frontend/declarations/main/main.did.d.ts | 9 ++++ frontend/declarations/main/main.did.js | 12 +++++ 8 files changed, 122 insertions(+), 17 deletions(-) diff --git a/backend/main/main-canister.mo b/backend/main/main-canister.mo index 18a673a2..3cf894de 100644 --- a/backend/main/main-canister.mo +++ b/backend/main/main-canister.mo @@ -51,18 +51,18 @@ actor { public type User = Types.User; public type PageCount = Nat; public type SemverPart = Types.SemverPart; + public type TestStats = Types.TestStats; let apiVersion = "1.2"; // (!) make changes in pair with cli var packageVersions = TrieMap.TrieMap(Text.equal, Text.hash); var packageOwners = TrieMap.TrieMap(Text.equal, Text.hash); + var highestConfigs = TrieMap.TrieMap(Text.equal, Text.hash); + var packageConfigs = TrieMap.TrieMap(Text.equal, Text.hash); var packagePublications = TrieMap.TrieMap(Text.equal, Text.hash); - var fileIdsByPackage = TrieMap.TrieMap(Text.equal, Text.hash); - - var packageConfigs = TrieMap.TrieMap(Text.equal, Text.hash); - var highestConfigs = TrieMap.TrieMap(Text.equal, Text.hash); + var packageTestStats = TrieMap.TrieMap(Text.equal, Text.hash); let downloadLog = DownloadLog.DownloadLog(); downloadLog.setTimers(); @@ -86,6 +86,7 @@ actor { }; let publishingPackages = TrieMap.TrieMap(Text.equal, Text.hash); let publishingFiles = TrieMap.TrieMap>(Text.equal, Text.hash); + let publishingTestStats = TrieMap.TrieMap(Text.equal, Text.hash); // PRIVATE func _getHighestVersion(name : PackageName) : ?PackageVersion { @@ -141,7 +142,6 @@ actor { downloadsInLast7Days = downloadLog.getDownloadsByPackageNameIn(config.name, 7 * DAY); downloadsInLast30Days = downloadLog.getDownloadsByPackageNameIn(config.name, 30 * DAY); downloadsTotal = downloadLog.getTotalDownloadsByPackageName(config.name); - versionDownloadsTotal = downloadLog.getTotalDownloadsByPackageId(packageId); }; }; }; @@ -159,6 +159,7 @@ actor { devDeps = _getPackageDevDependencies(name, version); dependents = _getPackageDependents(name); downloadTrend = downloadLog.getDownloadTrendByPackageName(name); + testStats = Option.get(packageTestStats.get(packageId), { passed = 0; passedNames = []; }); }; }; }; @@ -367,6 +368,16 @@ actor { await storageManager.uploadChunk(publishing.storage, fileId, chunkIndex, chunk); }; + public shared ({caller}) func uploadTestStats(publishingId : PublishingId, testStats : TestStats) : async Result.Result<(), Err> { + assert(Utils.isAuthorized(caller)); + + let ?publishing = publishingPackages.get(publishingId) else return #err("Publishing package not found"); + assert(publishing.user == caller); + + publishingTestStats.put(publishingId, testStats); + #ok; + }; + public shared ({caller}) func finishPublish(publishingId : PublishingId) : async Result.Result<(), Err> { assert(Utils.isAuthorized(caller)); @@ -420,8 +431,16 @@ actor { storage = publishing.storage; }); + switch (publishingTestStats.get(publishingId)) { + case (?testStats) { + packageTestStats.put(packageId, testStats); + }; + case (null) {}; + }; + publishingFiles.delete(publishingId); publishingPackages.delete(publishingId); + publishingTestStats.delete(publishingId); #ok; }; @@ -919,13 +938,14 @@ actor { let backupManager = Backup.BackupManager(backupState); type BackupChunk = { - #v1 : { + #v2 : { #packagePublications : [(PackageId, PackagePublication)]; #packageVersions : [(PackageName, [PackageVersion])]; #packageOwners : [(PackageName, Principal)]; #packageConfigs : [(PackageId, PackageConfigV2)]; #highestConfigs : [(PackageName, PackageConfigV2)]; #fileIdsByPackage : [(PackageId, [FileId])]; + #packageTestStats : [(PackageId, TestStats)]; #downloadLog : DownloadLog.Stable; #storageManager : StorageManager.Stable; #users : Users.Stable; @@ -943,17 +963,18 @@ actor { }; func _backup() : async () { - let backup = backupManager.NewBackup("v1"); + let backup = backupManager.NewBackup("v2"); await backup.startBackup(); - await backup.uploadChunk(to_candid(#v1(#packagePublications(Iter.toArray(packagePublications.entries()))) : BackupChunk)); - await backup.uploadChunk(to_candid(#v1(#packageVersions(Iter.toArray(packageVersions.entries()))) : BackupChunk)); - await backup.uploadChunk(to_candid(#v1(#packageOwners(Iter.toArray(packageOwners.entries()))) : BackupChunk)); - await backup.uploadChunk(to_candid(#v1(#fileIdsByPackage(Iter.toArray(fileIdsByPackage.entries()))) : BackupChunk)); - await backup.uploadChunk(to_candid(#v1(#downloadLog(downloadLog.toStable())) : BackupChunk)); - await backup.uploadChunk(to_candid(#v1(#storageManager(storageManager.toStable())) : BackupChunk)); - await backup.uploadChunk(to_candid(#v1(#users(users.toStable())) : BackupChunk)); - await backup.uploadChunk(to_candid(#v1(#highestConfigs(Iter.toArray(highestConfigs.entries()))) : BackupChunk)); - await backup.uploadChunk(to_candid(#v1(#packageConfigs(Iter.toArray(packageConfigs.entries()))) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#packagePublications(Iter.toArray(packagePublications.entries()))) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#packageVersions(Iter.toArray(packageVersions.entries()))) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#packageOwners(Iter.toArray(packageOwners.entries()))) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#fileIdsByPackage(Iter.toArray(fileIdsByPackage.entries()))) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#packageTestStats(Iter.toArray(packageTestStats.entries()))) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#downloadLog(downloadLog.toStable())) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#storageManager(storageManager.toStable())) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#users(users.toStable())) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#highestConfigs(Iter.toArray(highestConfigs.entries()))) : BackupChunk)); + await backup.uploadChunk(to_candid(#v2(#packageConfigs(Iter.toArray(packageConfigs.entries()))) : BackupChunk)); await backup.finishBackup(); }; @@ -963,7 +984,7 @@ actor { assert(Utils.isAdmin(caller)); await backupManager.restore(backupId, func(blob : Blob) { - let ?#v1(chunk) : ?BackupChunk = from_candid(blob) else Debug.trap("Failed to restore chunk"); + let ?#v2(chunk) : ?BackupChunk = from_candid(blob) else Debug.trap("Failed to restore chunk"); switch (chunk) { case (#packagePublications(packagePublicationsStable)) { @@ -978,6 +999,9 @@ actor { case (#fileIdsByPackage(fileIdsByPackageStable)) { fileIdsByPackage := TrieMap.fromEntries(fileIdsByPackageStable.vals(), Text.equal, Text.hash); }; + case (#packageTestStats(packageTestStatsStable)) { + packageTestStats := TrieMap.fromEntries(packageTestStatsStable.vals(), Text.equal, Text.hash); + }; case (#downloadLog(downloadLogStable)) { downloadLog.cancelTimers(); downloadLog.loadStable(downloadLogStable); @@ -1007,6 +1031,7 @@ actor { stable var highestConfigsStableV2 : [(PackageName, PackageConfigV2)] = []; stable var fileIdsByPackageStable : [(PackageId, [FileId])] = []; + stable var packageTestStatsStable : [(PackageId, TestStats)] = []; stable var downloadLogStable : DownloadLog.Stable = null; stable var storageManagerStable : StorageManager.Stable = null; @@ -1017,6 +1042,7 @@ actor { packageVersionsStable := Iter.toArray(packageVersions.entries()); packageOwnersStable := Iter.toArray(packageOwners.entries()); fileIdsByPackageStable := Iter.toArray(fileIdsByPackage.entries()); + packageTestStatsStable := Iter.toArray(packageTestStats.entries()); downloadLogStable := downloadLog.toStable(); storageManagerStable := storageManager.toStable(); usersStable := users.toStable(); @@ -1038,6 +1064,9 @@ actor { fileIdsByPackage := TrieMap.fromEntries(fileIdsByPackageStable.vals(), Text.equal, Text.hash); fileIdsByPackageStable := []; + packageTestStats := TrieMap.fromEntries(packageTestStatsStable.vals(), Text.equal, Text.hash); + packageTestStatsStable := []; + downloadLog.cancelTimers(); downloadLog.loadStable(downloadLogStable); downloadLog.setTimers(); diff --git a/backend/main/types.mo b/backend/main/types.mo index 468b2788..9b450431 100644 --- a/backend/main/types.mo +++ b/backend/main/types.mo @@ -87,6 +87,7 @@ module { devDeps : [PackageSummary]; dependents : [PackageSummary]; downloadTrend : [DownloadsSnapshot]; + testStats : TestStats; }; public type DownloadsSnapshot = { @@ -94,4 +95,9 @@ module { endTime : Time.Time; downloads : Nat; }; + + public type TestStats = { + passed : Nat; + passedNames : [Text]; + }; }; \ No newline at end of file diff --git a/cli/declarations/main/main.did b/cli/declarations/main/main.did index 9bf70288..ca55becc 100644 --- a/cli/declarations/main/main.did +++ b/cli/declarations/main/main.did @@ -26,6 +26,16 @@ type User = }; type Time = int; type Text = text; +type TestStats__1 = + record { + passed: nat; + passedNames: vec text; + }; +type TestStats = + record { + passed: nat; + passedNames: vec text; + }; type StorageStats = record { cyclesBalance: nat; @@ -100,6 +110,7 @@ type PackageSummary__1 = owner: principal; ownerInfo: User; publication: PackagePublication; + testStats: TestStats__1; }; type PackageSummary = record { @@ -110,6 +121,7 @@ type PackageSummary = owner: principal; ownerInfo: User; publication: PackagePublication; + testStats: TestStats__1; }; type PackagePublication = record { @@ -133,6 +145,7 @@ type PackageDetails = owner: principal; ownerInfo: User; publication: PackagePublication; + testStats: TestStats__1; versionHistory: vec PackageSummary__1; }; type PackageConfigV2__1 = @@ -242,4 +255,5 @@ service : { startPublish: (PackageConfigV2) -> (Result_1); takeAirdropSnapshot: () -> () oneway; uploadFileChunk: (PublishingId, FileId, nat, blob) -> (Result); + uploadTestStats: (PublishingId, TestStats) -> (Result); } diff --git a/cli/declarations/main/main.did.d.ts b/cli/declarations/main/main.did.d.ts index a6fe1c6e..c7a888c3 100644 --- a/cli/declarations/main/main.did.d.ts +++ b/cli/declarations/main/main.did.d.ts @@ -58,6 +58,7 @@ export interface PackageDetails { 'ownerInfo' : User, 'owner' : Principal, 'deps' : Array, + 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadTrend' : Array, @@ -79,6 +80,7 @@ export interface PackagePublication { export interface PackageSummary { 'ownerInfo' : User, 'owner' : Principal, + 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadsInLast7Days' : bigint, @@ -88,6 +90,7 @@ export interface PackageSummary { export interface PackageSummary__1 { 'ownerInfo' : User, 'owner' : Principal, + 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadsInLast7Days' : bigint, @@ -124,6 +127,11 @@ export interface StorageStats { 'cyclesBalance' : bigint, 'memorySize' : bigint, } +export interface TestStats { 'passedNames' : Array, 'passed' : bigint } +export interface TestStats__1 { + 'passedNames' : Array, + 'passed' : bigint, +} export type Text = string; export type Time = bigint; export interface User { @@ -206,4 +214,5 @@ export interface _SERVICE { [PublishingId, FileId, bigint, Uint8Array | number[]], Result >, + 'uploadTestStats' : ActorMethod<[PublishingId, TestStats], Result>, } diff --git a/cli/declarations/main/main.did.js b/cli/declarations/main/main.did.js index ea0147e4..2a52859c 100644 --- a/cli/declarations/main/main.did.js +++ b/cli/declarations/main/main.did.js @@ -36,6 +36,10 @@ export const idlFactory = ({ IDL }) => { 'githubVerified' : IDL.Bool, 'github' : IDL.Text, }); + const TestStats__1 = IDL.Record({ + 'passedNames' : IDL.Vec(IDL.Text), + 'passed' : IDL.Nat, + }); const Script = IDL.Record({ 'value' : IDL.Text, 'name' : IDL.Text }); const PackageName = IDL.Text; const DependencyV2 = IDL.Record({ @@ -69,6 +73,7 @@ export const idlFactory = ({ IDL }) => { const PackageSummary = IDL.Record({ 'ownerInfo' : User, 'owner' : IDL.Principal, + 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadsInLast7Days' : IDL.Nat, @@ -78,6 +83,7 @@ export const idlFactory = ({ IDL }) => { const PackageSummary__1 = IDL.Record({ 'ownerInfo' : User, 'owner' : IDL.Principal, + 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadsInLast7Days' : IDL.Nat, @@ -93,6 +99,7 @@ export const idlFactory = ({ IDL }) => { 'ownerInfo' : User, 'owner' : IDL.Principal, 'deps' : IDL.Vec(PackageSummary__1), + 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadTrend' : IDL.Vec(DownloadsSnapshot), @@ -145,6 +152,10 @@ export const idlFactory = ({ IDL }) => { }); const PublishingErr = IDL.Text; const Result_1 = IDL.Variant({ 'ok' : PublishingId, 'err' : PublishingErr }); + const TestStats = IDL.Record({ + 'passedNames' : IDL.Vec(IDL.Text), + 'passed' : IDL.Nat, + }); return IDL.Service({ 'backup' : IDL.Func([], [], []), 'claimAirdrop' : IDL.Func([IDL.Principal], [IDL.Text], []), @@ -237,6 +248,7 @@ export const idlFactory = ({ IDL }) => { [Result], [], ), + 'uploadTestStats' : IDL.Func([PublishingId, TestStats], [Result], []), }); }; export const init = ({ IDL }) => { return []; }; diff --git a/frontend/declarations/main/main.did b/frontend/declarations/main/main.did index 9bf70288..ca55becc 100644 --- a/frontend/declarations/main/main.did +++ b/frontend/declarations/main/main.did @@ -26,6 +26,16 @@ type User = }; type Time = int; type Text = text; +type TestStats__1 = + record { + passed: nat; + passedNames: vec text; + }; +type TestStats = + record { + passed: nat; + passedNames: vec text; + }; type StorageStats = record { cyclesBalance: nat; @@ -100,6 +110,7 @@ type PackageSummary__1 = owner: principal; ownerInfo: User; publication: PackagePublication; + testStats: TestStats__1; }; type PackageSummary = record { @@ -110,6 +121,7 @@ type PackageSummary = owner: principal; ownerInfo: User; publication: PackagePublication; + testStats: TestStats__1; }; type PackagePublication = record { @@ -133,6 +145,7 @@ type PackageDetails = owner: principal; ownerInfo: User; publication: PackagePublication; + testStats: TestStats__1; versionHistory: vec PackageSummary__1; }; type PackageConfigV2__1 = @@ -242,4 +255,5 @@ service : { startPublish: (PackageConfigV2) -> (Result_1); takeAirdropSnapshot: () -> () oneway; uploadFileChunk: (PublishingId, FileId, nat, blob) -> (Result); + uploadTestStats: (PublishingId, TestStats) -> (Result); } diff --git a/frontend/declarations/main/main.did.d.ts b/frontend/declarations/main/main.did.d.ts index a6fe1c6e..c7a888c3 100644 --- a/frontend/declarations/main/main.did.d.ts +++ b/frontend/declarations/main/main.did.d.ts @@ -58,6 +58,7 @@ export interface PackageDetails { 'ownerInfo' : User, 'owner' : Principal, 'deps' : Array, + 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadTrend' : Array, @@ -79,6 +80,7 @@ export interface PackagePublication { export interface PackageSummary { 'ownerInfo' : User, 'owner' : Principal, + 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadsInLast7Days' : bigint, @@ -88,6 +90,7 @@ export interface PackageSummary { export interface PackageSummary__1 { 'ownerInfo' : User, 'owner' : Principal, + 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadsInLast7Days' : bigint, @@ -124,6 +127,11 @@ export interface StorageStats { 'cyclesBalance' : bigint, 'memorySize' : bigint, } +export interface TestStats { 'passedNames' : Array, 'passed' : bigint } +export interface TestStats__1 { + 'passedNames' : Array, + 'passed' : bigint, +} export type Text = string; export type Time = bigint; export interface User { @@ -206,4 +214,5 @@ export interface _SERVICE { [PublishingId, FileId, bigint, Uint8Array | number[]], Result >, + 'uploadTestStats' : ActorMethod<[PublishingId, TestStats], Result>, } diff --git a/frontend/declarations/main/main.did.js b/frontend/declarations/main/main.did.js index ea0147e4..2a52859c 100644 --- a/frontend/declarations/main/main.did.js +++ b/frontend/declarations/main/main.did.js @@ -36,6 +36,10 @@ export const idlFactory = ({ IDL }) => { 'githubVerified' : IDL.Bool, 'github' : IDL.Text, }); + const TestStats__1 = IDL.Record({ + 'passedNames' : IDL.Vec(IDL.Text), + 'passed' : IDL.Nat, + }); const Script = IDL.Record({ 'value' : IDL.Text, 'name' : IDL.Text }); const PackageName = IDL.Text; const DependencyV2 = IDL.Record({ @@ -69,6 +73,7 @@ export const idlFactory = ({ IDL }) => { const PackageSummary = IDL.Record({ 'ownerInfo' : User, 'owner' : IDL.Principal, + 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadsInLast7Days' : IDL.Nat, @@ -78,6 +83,7 @@ export const idlFactory = ({ IDL }) => { const PackageSummary__1 = IDL.Record({ 'ownerInfo' : User, 'owner' : IDL.Principal, + 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadsInLast7Days' : IDL.Nat, @@ -93,6 +99,7 @@ export const idlFactory = ({ IDL }) => { 'ownerInfo' : User, 'owner' : IDL.Principal, 'deps' : IDL.Vec(PackageSummary__1), + 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadTrend' : IDL.Vec(DownloadsSnapshot), @@ -145,6 +152,10 @@ export const idlFactory = ({ IDL }) => { }); const PublishingErr = IDL.Text; const Result_1 = IDL.Variant({ 'ok' : PublishingId, 'err' : PublishingErr }); + const TestStats = IDL.Record({ + 'passedNames' : IDL.Vec(IDL.Text), + 'passed' : IDL.Nat, + }); return IDL.Service({ 'backup' : IDL.Func([], [], []), 'claimAirdrop' : IDL.Func([IDL.Principal], [IDL.Text], []), @@ -237,6 +248,7 @@ export const idlFactory = ({ IDL }) => { [Result], [], ), + 'uploadTestStats' : IDL.Func([PublishingId, TestStats], [Result], []), }); }; export const init = ({ IDL }) => { return []; }; From 133207aa8ea04dcb1c1b25511e422a75a727084f Mon Sep 17 00:00:00 2001 From: ZenVoich Date: Wed, 23 Aug 2023 12:41:07 +0400 Subject: [PATCH 3/5] [cli] upload package test stats --- cli/commands/publish.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cli/commands/publish.ts b/cli/commands/publish.ts index 051de543..3ab2ab14 100644 --- a/cli/commands/publish.ts +++ b/cli/commands/publish.ts @@ -228,15 +228,14 @@ export async function publish(options: {docs?: boolean, test?: boolean} = {}) { } // test + let reporter = new SilentReporter; if (options.test) { console.log('Running tests...'); - let reporter = new SilentReporter; await testWithReporter(reporter); if (reporter.failed > 0) { console.log(chalk.red('Error: ') + 'tests failed'); process.exit(1); } - // console.log(reporter.passedNamesFlat); } // progress @@ -258,6 +257,14 @@ export async function publish(options: {docs?: boolean, test?: boolean} = {}) { } let puiblishingId = publishing.ok; + // upload test stats + if (options.test) { + await actor.uploadTestStats(puiblishingId, { + passed: BigInt(reporter.passed), + passedNames: reporter.passedNamesFlat, + }); + } + // upload files await parallel(8, files, async (file: string) => { progress(); @@ -294,11 +301,11 @@ export async function publish(options: {docs?: boolean, test?: boolean} = {}) { // finish progress(); - // let res = await actor.finishPublish(puiblishingId); - // if ('err' in res) { - // console.log(chalk.red('Error: ') + res.err); - // return; - // } + let res = await actor.finishPublish(puiblishingId); + if ('err' in res) { + console.log(chalk.red('Error: ') + res.err); + return; + } console.log(chalk.green('Published ') + `${config.package.name}@${config.package.version}`); } \ No newline at end of file From a431fa9813b068741e8b09a3c373e4679ddc54b0 Mon Sep 17 00:00:00 2001 From: ZenVoich Date: Wed, 23 Aug 2023 12:41:17 +0400 Subject: [PATCH 4/5] [frontend] show package test stats --- frontend/components/Package.svelte | 7 +++++++ frontend/components/PackageRightPanel.svelte | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/components/Package.svelte b/frontend/components/Package.svelte index 447d819a..fc04f24b 100644 --- a/frontend/components/Package.svelte +++ b/frontend/components/Package.svelte @@ -168,6 +168,7 @@
selectTab('versions')}>Versions ({packageDetails.versionHistory.length})
selectTab('dependencies')}>Dependencies ({packageDetails.deps.length + githubDeps.length})
selectTab('dependents')}>Dependents ({packageDetails.dependents.length})
+
selectTab('tests')}>Tests ({packageDetails.testStats.passed})
@@ -228,6 +229,12 @@ {/each}
+ {:else if selectedTab == 'tests'} +
+ {#each packageDetails.testStats.passedNames as stat} +
{stat}
+ {/each} +
{/if}
diff --git a/frontend/components/PackageRightPanel.svelte b/frontend/components/PackageRightPanel.svelte index fffc4664..4cc8b532 100644 --- a/frontend/components/PackageRightPanel.svelte +++ b/frontend/components/PackageRightPanel.svelte @@ -25,12 +25,6 @@
{/if} - {#if packageDetails.config.documentation} - - {/if} {#if packageDetails.config.license}
License
From 94a445b80ce66a666bba622d507cf7957fb6ca16 Mon Sep 17 00:00:00 2001 From: ZenVoich Date: Thu, 24 Aug 2023 12:08:12 +0400 Subject: [PATCH 5/5] update declarations --- cli/declarations/main/main.did | 2 -- cli/declarations/main/main.did.d.ts | 2 -- cli/declarations/main/main.did.js | 10 ++++------ frontend/declarations/main/main.did | 2 -- frontend/declarations/main/main.did.d.ts | 2 -- frontend/declarations/main/main.did.js | 10 ++++------ 6 files changed, 8 insertions(+), 20 deletions(-) diff --git a/cli/declarations/main/main.did b/cli/declarations/main/main.did index ca55becc..aa849386 100644 --- a/cli/declarations/main/main.did +++ b/cli/declarations/main/main.did @@ -110,7 +110,6 @@ type PackageSummary__1 = owner: principal; ownerInfo: User; publication: PackagePublication; - testStats: TestStats__1; }; type PackageSummary = record { @@ -121,7 +120,6 @@ type PackageSummary = owner: principal; ownerInfo: User; publication: PackagePublication; - testStats: TestStats__1; }; type PackagePublication = record { diff --git a/cli/declarations/main/main.did.d.ts b/cli/declarations/main/main.did.d.ts index c7a888c3..033f8bd9 100644 --- a/cli/declarations/main/main.did.d.ts +++ b/cli/declarations/main/main.did.d.ts @@ -80,7 +80,6 @@ export interface PackagePublication { export interface PackageSummary { 'ownerInfo' : User, 'owner' : Principal, - 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadsInLast7Days' : bigint, @@ -90,7 +89,6 @@ export interface PackageSummary { export interface PackageSummary__1 { 'ownerInfo' : User, 'owner' : Principal, - 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadsInLast7Days' : bigint, diff --git a/cli/declarations/main/main.did.js b/cli/declarations/main/main.did.js index 2a52859c..c0a9d709 100644 --- a/cli/declarations/main/main.did.js +++ b/cli/declarations/main/main.did.js @@ -36,10 +36,6 @@ export const idlFactory = ({ IDL }) => { 'githubVerified' : IDL.Bool, 'github' : IDL.Text, }); - const TestStats__1 = IDL.Record({ - 'passedNames' : IDL.Vec(IDL.Text), - 'passed' : IDL.Nat, - }); const Script = IDL.Record({ 'value' : IDL.Text, 'name' : IDL.Text }); const PackageName = IDL.Text; const DependencyV2 = IDL.Record({ @@ -73,7 +69,6 @@ export const idlFactory = ({ IDL }) => { const PackageSummary = IDL.Record({ 'ownerInfo' : User, 'owner' : IDL.Principal, - 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadsInLast7Days' : IDL.Nat, @@ -83,13 +78,16 @@ export const idlFactory = ({ IDL }) => { const PackageSummary__1 = IDL.Record({ 'ownerInfo' : User, 'owner' : IDL.Principal, - 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadsInLast7Days' : IDL.Nat, 'config' : PackageConfigV2__1, 'publication' : PackagePublication, }); + const TestStats__1 = IDL.Record({ + 'passedNames' : IDL.Vec(IDL.Text), + 'passed' : IDL.Nat, + }); const DownloadsSnapshot = IDL.Record({ 'startTime' : Time, 'endTime' : Time, diff --git a/frontend/declarations/main/main.did b/frontend/declarations/main/main.did index ca55becc..aa849386 100644 --- a/frontend/declarations/main/main.did +++ b/frontend/declarations/main/main.did @@ -110,7 +110,6 @@ type PackageSummary__1 = owner: principal; ownerInfo: User; publication: PackagePublication; - testStats: TestStats__1; }; type PackageSummary = record { @@ -121,7 +120,6 @@ type PackageSummary = owner: principal; ownerInfo: User; publication: PackagePublication; - testStats: TestStats__1; }; type PackagePublication = record { diff --git a/frontend/declarations/main/main.did.d.ts b/frontend/declarations/main/main.did.d.ts index c7a888c3..033f8bd9 100644 --- a/frontend/declarations/main/main.did.d.ts +++ b/frontend/declarations/main/main.did.d.ts @@ -80,7 +80,6 @@ export interface PackagePublication { export interface PackageSummary { 'ownerInfo' : User, 'owner' : Principal, - 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadsInLast7Days' : bigint, @@ -90,7 +89,6 @@ export interface PackageSummary { export interface PackageSummary__1 { 'ownerInfo' : User, 'owner' : Principal, - 'testStats' : TestStats__1, 'downloadsTotal' : bigint, 'downloadsInLast30Days' : bigint, 'downloadsInLast7Days' : bigint, diff --git a/frontend/declarations/main/main.did.js b/frontend/declarations/main/main.did.js index 2a52859c..c0a9d709 100644 --- a/frontend/declarations/main/main.did.js +++ b/frontend/declarations/main/main.did.js @@ -36,10 +36,6 @@ export const idlFactory = ({ IDL }) => { 'githubVerified' : IDL.Bool, 'github' : IDL.Text, }); - const TestStats__1 = IDL.Record({ - 'passedNames' : IDL.Vec(IDL.Text), - 'passed' : IDL.Nat, - }); const Script = IDL.Record({ 'value' : IDL.Text, 'name' : IDL.Text }); const PackageName = IDL.Text; const DependencyV2 = IDL.Record({ @@ -73,7 +69,6 @@ export const idlFactory = ({ IDL }) => { const PackageSummary = IDL.Record({ 'ownerInfo' : User, 'owner' : IDL.Principal, - 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadsInLast7Days' : IDL.Nat, @@ -83,13 +78,16 @@ export const idlFactory = ({ IDL }) => { const PackageSummary__1 = IDL.Record({ 'ownerInfo' : User, 'owner' : IDL.Principal, - 'testStats' : TestStats__1, 'downloadsTotal' : IDL.Nat, 'downloadsInLast30Days' : IDL.Nat, 'downloadsInLast7Days' : IDL.Nat, 'config' : PackageConfigV2__1, 'publication' : PackagePublication, }); + const TestStats__1 = IDL.Record({ + 'passedNames' : IDL.Vec(IDL.Text), + 'passed' : IDL.Nat, + }); const DownloadsSnapshot = IDL.Record({ 'startTime' : Time, 'endTime' : Time,