Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: only and skip support #55

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions example_tests/only/only_case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Rhum } from "../../mod.ts";

Rhum.testPlan("test_plan_1", () => {
Rhum.testSuite("test_suite_1a", () => {
Rhum.testCase("test_case_1a1", () => {
Rhum.asserts.assertEquals(true, true);
});
Rhum.testCase("test_case_1a2", () => {
Rhum.asserts.assertEquals(true, true);
});
Rhum.testCase("test_case_1a3", () => {
Rhum.asserts.assertEquals(true, true);
});
});

Rhum.testSuite("test_suite_1b", () => {
Rhum.testCase("test_case_1b1", () => {
Rhum.asserts.assertEquals(true, true);
});
Rhum.only("test_case_1b2", () => {
Rhum.asserts.assertEquals(true, true);
});
Rhum.testCase("test_case_1b3", () => {
Rhum.asserts.assertEquals(true, true);
});
});
});

Rhum.run();
29 changes: 29 additions & 0 deletions example_tests/only/only_suite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Rhum } from "../../mod.ts";

Rhum.testPlan("test_plan_1", () => {
Rhum.testSuite("test_suite_1a", () => {
Rhum.testCase("test_case_1a1", () => {
Rhum.asserts.assertEquals(true, true);
});
Rhum.testCase("test_case_1a2", () => {
Rhum.asserts.assertEquals(true, true);
});
Rhum.testCase("test_case_1a3", () => {
Rhum.asserts.assertEquals(true, true);
});
});

Rhum.only("test_suite_1b", () => {
Rhum.testCase("test_case_1b1", () => {
Rhum.asserts.assertEquals(true, true);
});
Rhum.testCase("test_case_1b2", () => {
Rhum.asserts.assertEquals(true, true);
});
Rhum.testCase("test_case_1b3", () => {
Rhum.asserts.assertEquals(true, true);
});
});
});

Rhum.run();
150 changes: 103 additions & 47 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class RhumRunner {

protected test_suite_in_progress = "";

protected plan: ITestPlan = { suites: {} };
protected plan: ITestPlan = { suites: {}, only: false, skip: false };

// FILE MARKER - METHODS - CONSTRUCTOR ///////////////////////////////////////

Expand Down Expand Up @@ -225,9 +225,30 @@ export class RhumRunner {
}
}

// public only(cb: Function): void {
// // Do something
// }
/**
* Can be used for suites and cases. Will only run that.
* You can just switch a `Rhum.testSuite(...` to `Rhum,only(...`
*
* @param name - The name
* @param cb - The callback
*/
public only(name: string, cb: () => void): void {
if (this.passed_in_test_plan && this.passed_in_test_suite) { // is a test case being skipped, so do the same thing we would do in `testCase`
this.plan.suites[this.passed_in_test_suite].cases!.push({
name,
new_name: this.formatTestCaseName(name),
testFn: cb,
only: true,
skip: false
});
} else if (this.passed_in_test_plan) { // is a test suite being skipped, so do the same thing we could do in `testSuite`
this.passed_in_test_suite = name;
this.plan.suites![name] = {cases: [], only: true, skip: false};
cb();
this.passed_in_test_suite = "" // At the end of the suite, remove the name ready for a new suite. The reason for this is mainly when using `only`, so say when a 2nd suite uses `only`, it can flow through the logic properly
}
}


/**
* Allows a test plan, suite, or case to be skipped when the tests run.
Expand All @@ -249,9 +270,20 @@ export class RhumRunner {
* });
*/
public skip(name: string, cb: () => void): void {
// TODO(ebebbington|crookse) Maybe we could still call run, but pass in {
// ignore: true } which the Deno.Test will use? just so it displays ignored
// in the console
if (this.passed_in_test_plan && this.passed_in_test_suite) { // is a test case being skipped, so do the same thing we would do in `testCase`
this.plan.suites[this.passed_in_test_suite].cases!.push({
name,
new_name: this.formatTestCaseName(name),
testFn: cb,
only: false,
skip: true
});
} else if (this.passed_in_test_plan) { // is a test suite being skipped, so do the same thing we could do in `testSuite`
this.passed_in_test_suite = name;
this.plan.suites![name] = {cases: [], only: false, skip: true};
cb();
this.passed_in_test_suite = "" // At the end of the suite, remove the name ready for a new suite. The reason for this is mainly when using `only`, so say when a 2nd suite uses `only`, it can flow through the logic properly
}
}

/**
Expand Down Expand Up @@ -337,6 +369,8 @@ export class RhumRunner {
name,
new_name: this.formatTestCaseName(name),
testFn,
only: false,
skip: false
});
}

Expand Down Expand Up @@ -377,8 +411,9 @@ export class RhumRunner {
*/
public testSuite(name: string, testCases: () => void): void {
this.passed_in_test_suite = name;
this.plan.suites![name] = { cases: [] };
this.plan.suites![name] = { cases: [], only: false, skip: false };
testCases();
this.passed_in_test_suite = "" // At the end of the suite, remove the name ready for a new suite. The reason for this is mainly when using `only`, so say when a 2nd suite uses `only`, it can flow through the logic properly
}

/**
Expand All @@ -391,6 +426,33 @@ export class RhumRunner {
* Rhum.run();
*/
public run(): void {
//
// Validation for using `only`, mainly edge cases
//
const suitesWithOnly = Object.keys(this.plan.suites).filter(suiteName => this.plan.suites[suiteName].only === true)
const casesWithOnly: string[] = []
for (const suite in this.plan.suites) {
const onlyCases = this.plan.suites[suite].cases!.filter(c => c.only === true);
if (onlyCases.length) {
onlyCases.forEach(c => {
casesWithOnly.push(c.name)
})
}
}
// For when a user has set a suite and test case to only.. naughty
if (suitesWithOnly.length && casesWithOnly.length) {
throw new Error("A test suite and test case have been set to only in plan `" + this.passed_in_test_plan + ". Only one is allowed")
}
// Or when a user has two suites set to only... naughty
if (casesWithOnly.length > 1) {
throw new Error("Expected one test case as `only`, but " + casesWithOnly.length + " have been defined.")
}
// Or when a user has two cases seet to only.. naughty
if (suitesWithOnly.length > 1) {
throw new Error("Expected one test suite as `only`, but " + suitesWithOnly.length + " have been defined.")
}

// Run them
const tc = new TestCase(this.plan);
tc.run();
this.deconstruct();
Expand All @@ -408,45 +470,39 @@ export class RhumRunner {
* Returns the new test name for outputting purposes.
*/
protected formatTestCaseName(name: string): string {
let newName: string;
// (ebebbington) Unfortunately, due to the CI not correctly displaying output
// (it is all over the place and just completely unreadable as
// it doesn't play well with our control characters), we need to
// display the test output differently, based on if the tests are
// being ran inside a CI or not. Nothing will change for the current
// way of doing things, but if the tests are being ran inside a CI,
// the format would be:
// test <plan> | <suite> | <case> ... ok (2ms)
// test <plan> | <suite> | <case> ... ok (2ms)
// Even if plans and/or suites are the same. I believe this the best
// way we can display the output
if (Deno.env.get("CI") === "true") {
newName =
`${this.passed_in_test_plan} | ${this.passed_in_test_suite} | ${name}`;
return newName;
}
if (this.test_plan_in_progress != this.passed_in_test_plan) {
this.test_plan_in_progress = this.passed_in_test_plan;
this.test_suite_in_progress = this.passed_in_test_suite;
newName = `${"\u0008".repeat(name.length + extraChars)}` + // strip "test "
`${" ".repeat(name.length + extraChars)}` +
`\n${this.passed_in_test_plan}` +
`\n ${this.passed_in_test_suite}` +
`\n ${name} ... `;
} else {
if (this.test_suite_in_progress != this.passed_in_test_suite) {
this.test_suite_in_progress = this.passed_in_test_suite;
newName = `${"\u0008".repeat(name.length + extraChars)}` +
` ${this.passed_in_test_suite}` +
`${" ".repeat(name.length + extraChars)}` +
`\n ${name} ... `;
} else {
newName = `${"\u0008".repeat(name.length + extraChars)}` +
` ${name} ... `;
}
}
return `${this.passed_in_test_plan}` + "\n" +
` ${this.passed_in_test_suite}` + "\n" + // spaces are to keep it directly below plan name plus indentation
` ${name}`; // spaces are to keep it directly below plan name plus indentation

//
// Description of below code
//
// Another way to display the output. It displays just how
// it usually would, but wee include Deno's `test ` text, but
// separate into it's own column to be 'out of the way'. This method
// would be desirable if Deno removed the "test " part

// First test case for a new plan and suite
// if (this.test_plan_in_progress != this.passed_in_test_plan) {
// this.test_plan_in_progress = this.passed_in_test_plan;
// this.test_suite_in_progress = this.passed_in_test_suite;
// const newName = `| ${this.passed_in_test_plan}` +
// `\n | ${this.passed_in_test_suite}` +
// `\n | ${name}`;
// return newName;
// }

// Case for existing plan but new suite
// if (this.test_suite_in_progress != this.passed_in_test_suite) {
// this.test_suite_in_progress = this.passed_in_test_suite;
// const newName = `| ${this.passed_in_test_suite}` +
// `\n | ${name}`;
// return newName;
// }

return newName;
// Case for existing plan and suite
// const newName = `| ${name}`;
// return newName;
}

/**
Expand All @@ -458,7 +514,7 @@ export class RhumRunner {
this.passed_in_test_plan = "";
this.test_plan_in_progress = "";
this.test_suite_in_progress = "";
this.plan = { suites: {} };
this.plan = { suites: {}, only: false, skip: false};
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export interface ITestPlan {
suites: {
[key: string]: ITestSuite; // "key" is the suite name
};
only: boolean;
skip: boolean;
after_all_suite_hook?: () => void;
after_each_suite_hook?: () => void;
before_all_suite_hook?: () => void;
Expand All @@ -96,6 +98,8 @@ export interface ITestPlan {
*/
export interface ITestSuite {
cases?: ITestCase[];
only: boolean;
skip: boolean;
after_all_case_hook?: () => void;
after_each_case_hook?: () => void;
before_all_case_hook?: () => void;
Expand All @@ -117,6 +121,8 @@ export interface ITestSuite {
* argument of Deno.test().
*/
export interface ITestCase {
only: boolean;
skip: boolean;
name: string;
new_name: string;
testFn: () => void;
Expand Down
53 changes: 37 additions & 16 deletions src/test_case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class TestCase {
if (this.plan.hasOwnProperty("suites") === false) {
return;
}

Object.keys(this.plan.suites).forEach((suiteName) => {
// Run cases
this.plan!.suites[suiteName].cases!.forEach(async (c: ITestCase) => {
Expand Down Expand Up @@ -58,22 +59,42 @@ export class TestCase {
await this.plan.after_all_suite_hook();
}
};
// (ebebbington) To stop the output of test running being horrible
// in the CI, we will only display the new name which should be
// "plan | suite " case", as opposed to the "super saiyan"
// version. This name is generated differently inside `formatTestCaseName`
// based on if the tests are being ran inside a CI job
if (Deno.env.get("CI") === "true") {
await Deno.test(c.new_name, async () => {
await hookAttachedTestFn();
});
} else {
await Deno.test(c.name, async () => {
Deno.stdout.writeSync(encoder.encode(c.new_name));
await hookAttachedTestFn();
});

// Because lengths of test case names vary, for example:
//
// test plan
// test suite
// test case ... ok
// another test case ... ok
//
// We are going to make sure the "... ok" parts display in a nice column,
// by getting the length of the longest test case name, and ensuring each line is a consistent length
// that would match the total length of the longest test case name (plus any extra spaces), eg
//
// test plan
// test suite
// test case ... ok
// another test case ... ok
let longestCaseNameLen = 0;
for (const s in this.plan.suites) {
const len = Math.max(
...(this.plan.suites[s].cases!.map((c) => c.name.length)),
);
if (len > longestCaseNameLen) longestCaseNameLen = len;
}
});
});
const numberOfExtraSpaces = longestCaseNameLen - c.name.length; // for example, it would be 0 for when it's the test with the longest case name. It's just the character difference between the current case name and longest, telling us how many spaces to add

const only = this.plan.only || this.plan.suites[suiteName].only || c.only
const skip = this.plan.skip || this.plan.suites[suiteName].skip || c.skip
const ignore = skip === true || only === true
await Deno.test({
name: c.new_name + " ".repeat(numberOfExtraSpaces),
ignore: ignore,
async fn(): Promise<void> {
await hookAttachedTestFn();
}
})
})
})
}
}
Loading