Skip to content

Commit

Permalink
Adding TestIdDerive function (#375)
Browse files Browse the repository at this point in the history
* progress

* integrated into mainmenu

* incr ver
  • Loading branch information
timbrinded authored Feb 15, 2024
1 parent b7577aa commit 7a4a5d5
Show file tree
Hide file tree
Showing 15 changed files with 422 additions and 27 deletions.
6 changes: 6 additions & 0 deletions .changeset/tame-mice-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@moonwall/cli": patch
"@moonwall/tests": patch
---

Added Derive TestId feature
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ coverage
coverage.json
typechain
typechain-types
target/

cache
artifacts
Expand Down
1 change: 1 addition & 0 deletions packages/cli/moonwall.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/usr/bin/env -S node --no-warnings

import './dist/internal/logging.js'
import './dist/cmds/entrypoint.js'
17 changes: 14 additions & 3 deletions packages/cli/src/cmds/entrypoint.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import "../internal/logging";
import "@moonbeam-network/api-augment";
import dotenv from "dotenv";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { fetchArtifact } from "../internal/cmdFunctions/fetchArtifact";
import { generateConfig } from "../internal/cmdFunctions/initialisation";
import { fetchArtifact, deriveTestIds, generateConfig } from "../internal";
import { main } from "./main";
import { runNetworkCmd } from "./runNetwork";
import { testCmd } from "./runTests";
Expand Down Expand Up @@ -123,6 +121,19 @@ yargs(hideBin(process.argv))
await runNetworkCmd(argv as any);
}
)
.command<{ suitesRootDir: string }>(
"derive <suitesRootDir>",
"Derive test IDs based on positional order in the directory tree",
(yargs) => {
return yargs.positional("suitesRootDir", {
describe: "Root directory of the suites",
type: "string",
});
},
async (argv) => {
await deriveTestIds(argv.suitesRootDir);
}
)
.demandCommand(1)
.fail(async (msg) => {
console.log(msg);
Expand Down
45 changes: 39 additions & 6 deletions packages/cli/src/cmds/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ import { MoonwallConfig } from "@moonwall/types";
import chalk from "chalk";
import clear from "clear";
import colors from "colors";
import fs from "fs";
import inquirer from "inquirer";
import PressToContinuePrompt from "inquirer-press-to-continue";
import fetch from "node-fetch";
import path from "path";
import { SemVer, lt } from "semver";
import pkg from "../../package.json" assert { type: "json" };
import { fetchArtifact, getVersions } from "../internal/cmdFunctions/fetchArtifact";
import { createFolders, generateConfig } from "../internal/cmdFunctions/initialisation";
import {
createFolders,
deriveTestIds,
executeScript,
fetchArtifact,
generateConfig,
getVersions,
} from "../internal";
import { importAsyncConfig } from "../lib/configReader";
import { allReposAsync } from "../lib/repoDefinitions";
import { runNetworkCmd } from "./runNetwork";
import { testCmd } from "./runTests";
import fs from "fs";
import { executeScript } from "../internal/launcherCommon";
import path from "path";

inquirer.registerPrompt("press-to-continue", PressToContinuePrompt);

Expand Down Expand Up @@ -80,8 +85,14 @@ async function mainMenu(config?: MoonwallConfig) {
name: "4) Artifact Downloader: Fetch artifacts (x86) from GitHub repos",
value: "download",
},

{
name: "5) Rename TestIDs: Rename test id prefixes based on position in the directory tree",
value: "derive",
},

{
name: "5) Quit Application",
name: "6) Quit Application",
value: "quit",
},
],
Expand Down Expand Up @@ -143,6 +154,28 @@ async function mainMenu(config?: MoonwallConfig) {
return await resolveExecChoice(config);
}

case "derive": {
clear();
const { rootDir } = await inquirer.prompt({
name: "rootDir",
type: "input",
message: "Enter the root testSuites directory to process:",
default: "suites",
});
await deriveTestIds(rootDir);

await inquirer.prompt({
name: "test complete",
type: "press-to-continue",
anyKey: true,
pressToContinueMessage: `ℹ️ Renaming task for ${chalk.bold(
`/${rootDir}`
)} has been completed. Press any key to continue...\n`,
});

return false;
}

default:
throw new Error("Invalid choice");
}
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/internal/cmdFunctions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./downloader";
export * from "./fetchArtifact";
export * from "./initialisation";
export * from "./tempLogs";
134 changes: 134 additions & 0 deletions packages/cli/src/internal/deriveTestIds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import chalk from "chalk";
import fs from "fs";
import inquirer from "inquirer";
import path from "path";

export async function deriveTestIds(rootDir: string) {
const usedPrefixes: Set<string> = new Set();

try {
await fs.promises.access(rootDir, fs.constants.R_OK);
} catch (error) {
console.error(
`🔴 Error accessing directory ${chalk.bold(`/${rootDir}`)}, please sure this exists`
);
process.exitCode = 1;
return;
}
console.log(`🟢 Processing ${rootDir} ...`);
const topLevelDirs = getTopLevelDirs(rootDir);

const foldersToRename: { prefix: string; dir: string }[] = [];

for (const dir of topLevelDirs) {
const prefix = generatePrefix(dir, usedPrefixes);
foldersToRename.push({ prefix, dir });
}

const result = await inquirer.prompt({
type: "confirm",
name: "confirm",
message: `This will rename ${foldersToRename.length} suites IDs in ${rootDir}, continue?`,
});

if (!result.confirm) {
console.log("🔴 Aborted");
return;
}

for (const folder of foldersToRename) {
const { prefix, dir } = folder;
process.stdout.write(
`🟢 Changing suite ${dir} to use prefix ${chalk.bold(`(${prefix})`)} ....`
);

generateId(path.join(rootDir, dir), rootDir, prefix);
process.stdout.write(" Done ✅\n");
}

console.log(`🏁 Finished renaming rootdir ${chalk.bold(`/${rootDir}`)}`);
}

function getTopLevelDirs(rootDir: string): string[] {
return fs
.readdirSync(rootDir)
.filter((dir) => fs.statSync(path.join(rootDir, dir)).isDirectory());
}

function generatePrefix(directory: string, usedPrefixes: Set<string>): string {
let prefix = directory[0].toUpperCase();

if (usedPrefixes.has(prefix)) {
const match = directory.match(/[-_](\w)/);
if (match) {
// if directory name has a '-' or '_'
prefix += match[1].toUpperCase();
} else {
prefix = directory[1].toUpperCase();
}
}

while (usedPrefixes.has(prefix)) {
const charCode = prefix.charCodeAt(1);
if (charCode >= 90) {
// If it's Z, wrap around to A
prefix = `${String.fromCharCode(prefix.charCodeAt(0) + 1)}A`;
} else {
prefix = prefix[0] + String.fromCharCode(charCode + 1);
}
}

usedPrefixes.add(prefix);
return prefix;
}

function generateId(directory: string, rootDir: string, prefix: string): void {
const contents = fs.readdirSync(directory);

contents.sort((a, b) => {
const aIsDir = fs.statSync(path.join(directory, a)).isDirectory();
const bIsDir = fs.statSync(path.join(directory, b)).isDirectory();

if (aIsDir && !bIsDir) return -1;
if (!aIsDir && bIsDir) return 1;
return customFileSort(a, b);
});

let fileCount = 1;
let subDirCount = 1;

for (const item of contents) {
const fullPath = path.join(directory, item);

if (fs.statSync(fullPath).isDirectory()) {
const subDirPrefix = `0${subDirCount}`.slice(-2);
generateId(fullPath, rootDir, prefix + subDirPrefix);
subDirCount++;
} else {
const fileContent = fs.readFileSync(fullPath, "utf-8");
if (fileContent.includes("describeSuite")) {
const newId = prefix + `0${fileCount}`.slice(-2);
const updatedContent = fileContent.replace(
/(describeSuite\s*?\(\s*?\{\s*?id\s*?:\s*?['"])[^'"]+(['"])/,
`$1${newId}$2`
);
fs.writeFileSync(fullPath, updatedContent);
}
fileCount++;
}
}
}

function hasSpecialCharacters(filename: string): boolean {
return /[ \t!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+/.test(filename);
}

function customFileSort(a: string, b: string): number {
const aHasSpecialChars = hasSpecialCharacters(a);
const bHasSpecialChars = hasSpecialCharacters(b);

if (aHasSpecialChars && !bHasSpecialChars) return -1;
if (!aHasSpecialChars && bHasSpecialChars) return 1;

return a.localeCompare(b, undefined, { sensitivity: "accent" });
}
6 changes: 4 additions & 2 deletions packages/cli/src/internal/foundations/chopsticksHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ApiTypes, AugmentedEvent } from "@polkadot/api/types";
import { FrameSystemEventRecord } from "@polkadot/types/lookup";
import chalk from "chalk";
import { setTimeout } from "timers/promises";
import { assert } from "vitest";
import { MoonwallContext } from "../../lib/globalContext";

export async function getWsFromConfig(providerName?: string): Promise<WsProvider> {
Expand Down Expand Up @@ -93,7 +92,10 @@ export async function createChopsticksBlock(
}
return found;
});
assert(match, "Expected events not present in block");

if (!match) {
throw new Error("Expected events not present in block");
}
}

if (options && options.allowFailures === true) {
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/internal/foundations/devModeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { EventRecord } from "@polkadot/types/interfaces";
import chalk from "chalk";
import Debug from "debug";
import { setTimeout } from "timers/promises";
import { assert } from "vitest";
import {
getEnvironmentFromConfig,
importAsyncConfig,
Expand Down Expand Up @@ -204,7 +203,10 @@ export async function createDevBlock<
}
return found;
});
assert(match, "Expected events not present in block");

if (!match) {
throw new Error("Expected events not present in block");
}
}

if (!options.allowFailures) {
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/internal/foundations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./chopsticksHelpers";
export * from "./devModeHelpers";
export * from "./zombieHelpers";
10 changes: 10 additions & 0 deletions packages/cli/src/internal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export * from "./logging";
export * from "./cmdFunctions";
export * from "./commandParsers";
export * from "./deriveTestIds";
export * from "./fileCheckers";
export * from "./foundations";
export * from "./launcherCommon";
export * from "./localNode";
export * from "./processHelpers";
export * from "./providerFactories";
28 changes: 14 additions & 14 deletions packages/cli/src/lib/governanceProcedures.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import "@moonbeam-network/api-augment";
import { expect } from "vitest";
import type { ApiPromise } from "@polkadot/api";
import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types";
import { KeyringPair } from "@polkadot/keyring/types";
import {
PalletDemocracyReferendumInfo,
PalletReferendaReferendumInfo,
} from "@polkadot/types/lookup";
import { blake2AsHex } from "@polkadot/util-crypto";
import { DevModeContext } from "@moonwall/types";
import {
GLMR,
alith,
Expand All @@ -19,9 +11,15 @@ import {
filterAndApply,
signAndSend,
} from "@moonwall/util";
import { DevModeContext } from "@moonwall/types";
import { fastFowardToNextEvent } from "../internal/foundations/devModeHelpers";
import { ISubmittableResult } from "@polkadot/types/types";
import type { ApiPromise } from "@polkadot/api";
import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types";
import { KeyringPair } from "@polkadot/keyring/types";
import {
PalletDemocracyReferendumInfo,
PalletReferendaReferendumInfo,
} from "@polkadot/types/lookup";
import { blake2AsHex } from "@polkadot/util-crypto";
import { fastFowardToNextEvent } from "../internal";

export const COUNCIL_MEMBERS: KeyringPair[] = [baltathar, charleth, dorothy];
export const COUNCIL_THRESHOLD = Math.ceil((COUNCIL_MEMBERS.length * 2) / 3);
Expand Down Expand Up @@ -445,8 +443,10 @@ export const execTechnicalCommitteeProposal = async <
return proposalResult;
}

expect(proposalResult.successful, `Council proposal refused: ${proposalResult?.error?.name}`).to
.be.true;
if (!proposalResult.successful) {
throw `Council proposal refused: ${proposalResult?.error?.name}`;
}

const proposalHash = proposalResult.events
.find(({ event: { method } }) => method.toString() === "Proposed")
?.event.data[2].toHex();
Expand Down
Loading

0 comments on commit 7a4a5d5

Please sign in to comment.