Skip to content

Commit

Permalink
Merge pull request #135 from ZenVoich/feat/package-test-stats
Browse files Browse the repository at this point in the history
Feature: package test stats
  • Loading branch information
ZenVoich authored Aug 25, 2023
2 parents d90341c + 94a445b commit 4a229cc
Show file tree
Hide file tree
Showing 20 changed files with 270 additions and 54 deletions.
63 changes: 46 additions & 17 deletions backend/main/main-canister.mo
Original file line number Diff line number Diff line change
Expand Up @@ -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<PackageName, [PackageVersion]>(Text.equal, Text.hash);
var packageOwners = TrieMap.TrieMap<PackageName, Principal>(Text.equal, Text.hash);
var highestConfigs = TrieMap.TrieMap<PackageName, PackageConfigV2>(Text.equal, Text.hash);

var packageConfigs = TrieMap.TrieMap<PackageId, PackageConfigV2>(Text.equal, Text.hash);
var packagePublications = TrieMap.TrieMap<PackageId, PackagePublication>(Text.equal, Text.hash);

var fileIdsByPackage = TrieMap.TrieMap<PackageId, [FileId]>(Text.equal, Text.hash);

var packageConfigs = TrieMap.TrieMap<PackageId, PackageConfigV2>(Text.equal, Text.hash);
var highestConfigs = TrieMap.TrieMap<PackageName, PackageConfigV2>(Text.equal, Text.hash);
var packageTestStats = TrieMap.TrieMap<PackageId, TestStats>(Text.equal, Text.hash);

let downloadLog = DownloadLog.DownloadLog();
downloadLog.setTimers();
Expand All @@ -86,6 +86,7 @@ actor {
};
let publishingPackages = TrieMap.TrieMap<PublishingId, PublishingPackage>(Text.equal, Text.hash);
let publishingFiles = TrieMap.TrieMap<PublishingId, Buffer.Buffer<PublishingFile>>(Text.equal, Text.hash);
let publishingTestStats = TrieMap.TrieMap<PublishingId, TestStats>(Text.equal, Text.hash);

// PRIVATE
func _getHighestVersion(name : PackageName) : ?PackageVersion {
Expand Down Expand Up @@ -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);
};
};
};
Expand All @@ -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 = []; });
};
};
};
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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;
Expand All @@ -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();
};

Expand All @@ -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)) {
Expand All @@ -978,6 +999,9 @@ actor {
case (#fileIdsByPackage(fileIdsByPackageStable)) {
fileIdsByPackage := TrieMap.fromEntries<PackageId, [FileId]>(fileIdsByPackageStable.vals(), Text.equal, Text.hash);
};
case (#packageTestStats(packageTestStatsStable)) {
packageTestStats := TrieMap.fromEntries<PackageId, TestStats>(packageTestStatsStable.vals(), Text.equal, Text.hash);
};
case (#downloadLog(downloadLogStable)) {
downloadLog.cancelTimers();
downloadLog.loadStable(downloadLogStable);
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -1038,6 +1064,9 @@ actor {
fileIdsByPackage := TrieMap.fromEntries<PackageId, [FileId]>(fileIdsByPackageStable.vals(), Text.equal, Text.hash);
fileIdsByPackageStable := [];

packageTestStats := TrieMap.fromEntries<PackageId, TestStats>(packageTestStatsStable.vals(), Text.equal, Text.hash);
packageTestStatsStable := [];

downloadLog.cancelTimers();
downloadLog.loadStable(downloadLogStable);
downloadLog.setTimers();
Expand Down
6 changes: 6 additions & 0 deletions backend/main/types.mo
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,17 @@ module {
devDeps : [PackageSummary];
dependents : [PackageSummary];
downloadTrend : [DownloadsSnapshot];
testStats : TestStats;
};

public type DownloadsSnapshot = {
startTime : Time.Time;
endTime : Time.Time;
downloads : Nat;
};

public type TestStats = {
passed : Nat;
passedNames : [Text];
};
};
11 changes: 4 additions & 7 deletions cli/cli.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -188,14 +189,10 @@ program
program
.command('test [filter]')
.description('Run tests')
.option('-r, --reporter <reporter>', 'Choose reporter: verbose, compact, files')
.addOption(new Option('-r, --reporter <reporter>', 'Test reporter').choices(['verbose', 'compact', 'files', 'silent']).default('verbose'))
.addOption(new Option('--mode <mode>', 'Test mode').choices(['interpreter', 'wasi']).default('interpreter'))
.option('-w, --watch', 'Enable watch mode')
.option('--mode <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);
});

Expand Down
54 changes: 39 additions & 15 deletions cli/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,37 @@ 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;
}

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);
}
}

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -207,18 +212,29 @@ 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
let reporter = new SilentReporter;
if (options.test) {
console.log('Running tests...');
await testWithReporter(reporter);
if (reporter.failed > 0) {
console.log(chalk.red('Error: ') + 'tests failed');
process.exit(1);
}
}

Expand All @@ -227,7 +243,7 @@ export async function publish({noDocs = false} = {}) {
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
Expand All @@ -241,6 +257,14 @@ export async function publish({noDocs = false} = {}) {
}
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();
Expand Down
Loading

0 comments on commit 4a229cc

Please sign in to comment.