testarmada-magellan
Version:
Massively parallel automated testing
769 lines (619 loc) • 20.8 kB
JavaScript
;
const _ = require("lodash")
const analytics = require("../src/global_analytics");
const fs = require("fs");
const logger = require("../src/logger");
const mkdirSync = require("../src/util/mkdir_sync");
const path = require("path");
const settings = require("../src/settings");
const ChildProcessHandler = require("../src/util/childProcess");
const Reporter = require("../src/reporters/stdout/reporter");
const TestRunner = require("../src/test_runner");
const Test = require("../src/test");
const TestQueue = require("../src/test_queue");
jest.mock("../src/global_analytics", () => {
return {
push: () => { },
mark: () => { }
};
});
jest.mock("fs");
jest.mock("../src/util/childProcess");
jest.mock("../src/reporters/stdout/reporter");
describe("test_runner", () => {
let tests = [];
let options = {};
beforeEach(() => {
tests = [{ filename: "tests/demo-web.js" }];
options = {
debug: false,
maxWorkers: 1,
maxTestAttempts: 3,
profiles:
[{
desiredCapabilities: {},
nightwatchEnv: "invisible_chrome",
id: "invisible_chrome",
executor: "local"
}],
executors:
{
local:
{
name: "testarmada-magellan-local-executor",
shortName: "local",
execute: () => { }
}
},
listeners: [],
strategies: {
bail:
{
hasBailed: false,
name: "testarmada-magellan-fast-bail-strategy",
description: "Magellan will bail as long as one test fails",
bailReason: "At least one test has failed",
shouldBail: () => { },
getBailReason: () => "FAKE_BAIL_REASON",
decide: () => false
},
resource: {
holdTestResource: (opts) => Promise.resolve(),
releaseTestResource: (opts) => Promise.resolve()
}
},
serial: true,
allocator: {
get: (cb) => cb(null, { index: 1 }),
release: (worker) => true
},
onFinish: () => Promise.resolve()
}
})
test("constructor", () => {
const testRunner = initTestRunner(tests, options);
});
describe("stageTestHandler", () => {
test("should stage test properly", (done) => {
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
testRunner.runTest = (test, worker) => Promise.resolve({ erroror: false });
testRunner.stageTestHandler(test, (error, test) => {
expect(error).toBeNull();
expect(test.erroror).toBe(false);
done();
});
});
test("should have erroror in cb test setup errors out", (done) => {
const testRunner = initTestRunner(tests, options);
const test = stubFailTest();
testRunner.runTest = (test, worker) => Promise.resolve({ erroror: false });
testRunner.stageTestHandler(test, (error, test) => {
expect(error).toBe("FAKE_ERROR");
done();
});
});
test("test run exception results in test failure", (done) => {
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
testRunner.runTest = (test, worker) => Promise.reject({ erroror: true });
testRunner.stageTestHandler(test, (error, test) => {
expect(error).toBeDefined();
expect(test.fail).toHaveBeenCalled();
done();
});
});
test("test run error results in test failure", (done) => {
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
testRunner.runTest = (test, worker) => Promise.resolve({ error: true });
testRunner.stageTestHandler(test, (error, test) => {
expect(error).toBeDefined();
expect(test.fail).toHaveBeenCalled();
done();
});
});
test("catch runtime resource error", (done) => {
options.strategies.resource.holdTestResource = () => Promise.reject("FAILURE");
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
testRunner.stageTestHandler(test, (error, test) => {
expect(error).toBeDefined();
done();
});
});
test("worker error results in test failure", (done) => {
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
testRunner.allocator.get = (cb) => cb(true, { index: 1 });
testRunner.stageTestHandler(test, (error, test) => {
expect(error).toBeDefined();
done();
});
});
});
describe("completeTestHandler", () => {
test("successful test", (done) => {
const testRunner = initTestRunner(tests, options);
const error = jest.fn();
testRunner.completeTestHandler(error, {
status: Test.TEST_STATUS_SUCCESSFUL,
canRun: () => true,
maxAttempts: 3,
attempts: 2,
workerIndex: 1,
getRuntime: () => 3
});
expect(error).not.toHaveBeenCalled();
done();
});
test("failed test", (done) => {
options.strategies.bail.hasBailed = false;
options.strategies.bail.shouldBail = function () { this.hasBailed = true; };
const testRunner = initTestRunner(tests, options);
testRunner.completeTestHandler(null, {
status: Test.TEST_STATUS_FAILED,
canRun: () => true,
maxAttempts: 3,
attempts: 2,
workerIndex: 1,
getRuntime: () => 3
})
.then((v) => {
expect(v).toBe(1);
done();
});
});
test("bailed test", (done) => {
options.strategies.bail.hasBailed = true;
const testRunner = initTestRunner(tests, options);
const test = {
status: Test.TEST_STATUS_SUCCESSFUL,
canRun: () => true,
maxAttempts: 3,
attempts: 2,
workerIndex: 1,
getRuntime: () => 3
};
testRunner.completeTestHandler(null, test);
expect(test.status).toBe(Test.TEST_STATUS_SKIPPED)
done();
});
test("new test", (done) => {
const testRunner = initTestRunner(tests, options);
const error = jest.fn();
testRunner.completeTestHandler(error, {
status: Test.TEST_STATUS_NEW,
canRun: () => true,
maxAttempts: 3,
attempts: 2,
workerIndex: 1,
getRuntime: () => 3
});
expect(error).not.toHaveBeenCalled();
done();
});
test("test that fail are enqueued again", (done) => {
const testRunner = initTestRunner(tests, options);
const error = jest.fn();
const queueEnqueueSpy = jest.spyOn(testRunner.queue, "enqueue");
testRunner.completeTestHandler(error, {
status: Test.TEST_STATUS_FAILED,
canRun: () => false,
maxAttempts: 3,
attempts: 2,
workerIndex: 1,
getRuntime: () => 3
});
expect(queueEnqueueSpy).toHaveBeenCalled();
done();
});
});
describe("completeQueueHandler", () => {
beforeEach(() => {
jest.useFakeTimers();
});
test("listeners are successfully resolved", (done) => {
const listener = new Reporter();
listener.flush = () => Promise.resolve(10);
const listenerFlushSpy = jest.spyOn(listener, "flush");
options.listeners = [listener];
const testRunner = initTestRunner(tests, options);
testRunner.completeQueueHandler();
jest.runAllTimers();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(listenerFlushSpy).toHaveBeenCalled();
done();
});
test("listeners are successfully resolved even when flush has error", (done) => {
const listener = new Reporter();
listener.flush = () => Promise.reject();
const listenerFlushSpy = jest.spyOn(listener, "flush");
options.listeners = [listener];
const testRunner = initTestRunner(tests, options);
testRunner.completeQueueHandler();
jest.runAllTimers();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(listenerFlushSpy).toHaveBeenCalled();
done();
});
});
describe("run", () => {
test("run is executed successfully", (done) => {
const testRunner = initTestRunner(tests, options);
testRunner.queue.proceed = jest.fn();
testRunner.run();
expect(testRunner.queue.proceed).toHaveBeenCalled();
done();
});
test("run in serial is executed successfully", (done) => {
const testRunner = initTestRunner(tests, options);
testRunner.serial = true;
testRunner.queue.proceed = jest.fn();
testRunner.run();
expect(testRunner.queue.proceed).toHaveBeenCalled();
done();
});
});
describe("runTest", () => {
beforeEach(() => {
jest.useFakeTimers();
});
test("test is run successfully", (done) => {
fs.mkdirSync.mockImplementation((p) => { });
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
let worker = {
index: 1,
occupied: true,
portOffset: 12000
};
testRunner.execute = (testRun, test) => Promise.resolve({ error: false });
const executeSpy = jest.spyOn(testRunner, "execute");
testRunner.runTest(test, worker).then((v) => {
expect(executeSpy).toHaveBeenCalledTimes(1);
done();
});
jest.runAllTimers();
});
test("test in serial is run successfully", (done) => {
fs.mkdirSync.mockImplementation((p) => { });
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
let worker = {
index: 1,
occupied: true,
portOffset: 12000,
token: "SOME_TOKEN"
};
testRunner.execute = (testRun, test) => Promise.resolve({ error: false });
const executeSpy = jest.spyOn(testRunner, "execute");
testRunner.serial = true;
testRunner.runTest(test, worker).then((v) => {
expect(executeSpy).toHaveBeenCalledTimes(1);
done();
});
jest.runAllTimers();
});
test("failed test is rejected", (done) => {
fs.mkdirSync.mockImplementation((p) => { });
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
let worker = {
index: 1,
occupied: true,
portOffset: 12000,
token: "SOME_TOKEN"
};
testRunner.execute = (testRun, test) => Promise.reject("EXECUTION_ERROR");
const executeSpy = jest.spyOn(testRunner, "execute");
testRunner.runTest(test, worker).catch((v) => {
expect(executeSpy).toHaveBeenCalledTimes(1);
done();
});
jest.runAllTimers();
});
test("empty buildId is rejected", (done) => {
fs.mkdirSync.mockImplementation((p) => { });
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
let worker = {
index: 1,
occupied: true,
portOffset: 12000,
token: "SOME_TOKEN"
};
testRunner.buildId = null;
testRunner.runTest(test, worker).catch((v) => {
done();
});
jest.runAllTimers();
});
});
describe("execute", () => {
beforeEach(() => {
jest.useFakeTimers();
});
test("test is executed successfully", (done) => {
const listener = new Reporter();
const listenerListenToSpy = jest.spyOn(listener, "listenTo");
options.listeners = [listener];
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
const testRun = {
guid: "",
getEnvironment: (opts) => ""
};
testRunner.execute(testRun, test);
jest.runOnlyPendingTimers();
jest.runOnlyPendingTimers();
expect(listenerListenToSpy).toHaveBeenCalled();
done();
});
test("enable executor", (done) => {
const listener = new Reporter();
const listenerListenToSpy = jest.spyOn(listener, "listenTo");
options.listeners = [listener];
const testRunner = initTestRunner(tests, options);
testRunner.queue.isIdle = () => false;
const test = stubPassTest();
const testRun = {
enableExecutor: (executor) => { },
guid: "",
getEnvironment: (opts) => ""
};
testRunner.execute(testRun, test);
jest.runOnlyPendingTimers();
jest.runOnlyPendingTimers();
expect(listenerListenToSpy).toHaveBeenCalled();
done();
});
test("failed test with time out", (done) => {
const listener = new Reporter();
const listenerListenToSpy = jest.spyOn(listener, "listenTo");
options.listeners = [listener];
const testRunner = initTestRunner(tests, options);
const test = stubFailTest();
const testRun = {
guid: "",
getEnvironment: (opts) => ""
};
testRunner.execute(testRun, test);
jest.runOnlyPendingTimers();
jest.runOnlyPendingTimers();
expect(listenerListenToSpy).toHaveBeenCalled();
done();
});
test("bailing a test closes the execution worker", (done) => {
const listener = new Reporter();
const listenerListenToSpy = jest.spyOn(listener, "listenTo");
options.listeners = [listener];
options.strategies.bail.hasBailed = true;
options.strategies.bail.getBailReason = jest.fn();
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
const testRun = {
guid: "",
getEnvironment: (opts) => ""
};
testRunner.execute(testRun, test);
jest.runOnlyPendingTimers();
jest.runOnlyPendingTimers();
expect(listenerListenToSpy).toHaveBeenCalled();
expect(options.strategies.bail.getBailReason).toHaveBeenCalled();
done();
});
test("test environment error results in promise rejection", () => {
const listener = new Reporter();
const listenerListenToSpy = jest.spyOn(listener, "listenTo");
options.listeners = [listener];
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
const testRun = {
guid: "",
getEnvironment: (opts) => {
throw "ENVIRONMENT_ERROR";
}
};
return testRunner.execute(testRun, test).catch(e => expect(e).toMatch("ENVIRONMENT_ERROR"));
});
test("child process error results in promise rejection", () => {
const listener = new Reporter();
const listenerListenToSpy = jest.spyOn(listener, "listenTo");
options.listeners = [listener];
options.executors.local.execute = () => {
throw "CHILD_PROCESS_ERROR";
};
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
const testRun = {
guid: "",
getEnvironment: (opts) => ""
};
return testRunner.execute(testRun, test).catch(e => expect(e).toMatch("CHILD_PROCESS_ERROR"));
});
test("listener error results in promise rejection", () => {
const listener = new Reporter();
listener.listenTo = () => {
throw "LISTENER_ERROR";
};
options.listeners = [listener];
const testRunner = initTestRunner(tests, options);
const test = stubPassTest();
const testRun = {
guid: "",
getEnvironment: (opts) => ""
};
return testRunner.execute(testRun, test).catch(e => expect(e).toMatch("LISTENER_ERROR"));
});
});
describe("logTestsSummary", () => {
test("empty test print no warning", (done) => {
const warnSpy = jest.spyOn(logger, "warn");
const testRunner = initTestRunner(tests, options);
testRunner.logTestsSummary();
expect(warnSpy).not.toHaveBeenCalled();
done();
});
test("passing 1 test and failing 1 test prints 4 warnings", (done) => {
const loggerLogSpy = jest.spyOn(logger, "log");
const loggerWarnSpy = jest.spyOn(logger, "warn");
const analyticsMarkSpy = jest.spyOn(analytics, "mark");
const testRunner = initTestRunner(tests, options);
enqueuePassedTest(testRunner);
enqueueFailedTest(testRunner);
testRunner.logTestsSummary();
expect(loggerLogSpy).toHaveBeenCalled();
expect(loggerWarnSpy).toHaveBeenCalled();
expect(analyticsMarkSpy).toHaveBeenCalledWith("magellan-run", "failed");
done();
});
test("failing 1 test with bail prints 4 warnings with bail reason", (done) => {
options.strategies.bail.hasBailed = true;
options.strategies.bail.getBailReason = () => "Some bail reason";
const loggerLogSpy = jest.spyOn(logger, "log");
const loggerWarnSpy = jest.spyOn(logger, "warn");
const analyticsMarkSpy = jest.spyOn(analytics, "mark");
const bailReasonSpy = jest.spyOn(options.strategies.bail, "getBailReason");
const testRunner = initTestRunner(tests, options);
testRunner.queue = new TestQueue({
tests: [{
status: Test.TEST_STATUS_SUCCESSFUL,
getRetries: () => 2,
}, {
status: Test.TEST_STATUS_FAILED,
getRetries: () => 2,
}, {
status: Test.TEST_STATUS_NEW,
getRetries: () => 0,
}],
getTestAmount: () => 3,
getPassedTests: () => 1,
getFailedTests: () => 1,
workerAmount: 1,
completeQueueHandler: () => Promise.resolve(1),
stageTestHandler: (test, cb) => cb()
});
testRunner.logTestsSummary();
expect(loggerLogSpy).toHaveBeenCalled();
expect(loggerWarnSpy).toHaveBeenCalled();
expect(bailReasonSpy).toHaveBeenCalled();
expect(analyticsMarkSpy).toHaveBeenCalledWith("magellan-run", "failed");
done();
});
test("if enablePassedTestsLogging enabled, print out the success logs if all tests pass", (done) => {
const loggerLogSpy = jest.spyOn(logger, "log");
const loggerWarnSpy = jest.spyOn(logger, "warn");
const testRunner = initTestRunner(tests, options, { enablePassedTestsLogging: true });
enqueuePassedTest(testRunner);
testRunner.logTestsSummary();
expect(loggerLogSpy).toHaveBeenCalled();
expect(loggerWarnSpy).toHaveBeenCalled();
done();
});
});
test("gather trends", () => {
const loggerLogSpy = jest.spyOn(logger, "log");
const testRunner = initTestRunner(tests, options);
jest.mock("fs", () => {
return {
readFileSync: () => {
console.log("some fake call")
return {
failures: {
"FAKE_FAILURE": 2,
"ANOTHER_FAILURE": 1
}
}
},
writeFileSync: () => { }
}
});
testRunner.trends = {
failures: {
"FAKE_FAILURE": 1
}
};
testRunner.gatherTrends();
expect(loggerLogSpy).toHaveBeenCalled();
});
});
function initTestRunner(tests, options, settings) {
const testRunner = new TestRunner(tests, options, {
settings: Object.assign({
gatherTrends: true,
debugVerbose: true,
buildId: "FAKE_BUILD_ID",
testFramework: {
iterator: () => ["a", "b", "c"],
filters: {
a: () => true,
b: () => true
},
TestRun: class { }
},
BASE_PORT_SPACING: 3
}, settings),
startTime: (new Date()).getTime()
});
testRunner.queue = new TestQueue({
tests: [],
workerAmount: 1,
completeQueueHandler: () => Promise.resolve(1),
stageTestHandler: (test, cb) => cb()
});
return testRunner;
}
function enqueuePassedTest(testRunner) {
testRunner.queue.tests.push({
status: Test.TEST_STATUS_SUCCESSFUL,
getRetries: () => 0
});
}
function enqueueFailedTest(testRunner) {
testRunner.queue.tests.push({
status: Test.TEST_STATUS_FAILED,
getRetries: () => 2
});
}
function stubPassTest() {
return {
executor: {
shortName: "FAKE_EXECUTOR_SHORT_NAME",
setupTest: (cb) => cb(null, "FAKE_TOKEN"),
teardownTest: (token, cb) => cb(),
summerizeTest: () => { },
getPorts: jest.fn()
},
profile: {
executor: "local"
},
locator: "",
startClock: () => { },
stopClock: () => { },
getRuntime: () => 0,
stdout: () => { },
pass: jest.fn(),
fail: jest.fn()
}
}
function stubFailTest() {
return {
executor: {
setupTest: (cb) => cb("FAKE_ERROR", "FAKE_TOKEN"),
teardownTest: (token, cb) => cb(),
summerizeTest: (buildId, metadata, cb) => cb(),
getPorts: jest.fn()
},
profile: {
executor: "local"
},
locator: "",
startClock: () => { },
stopClock: () => { },
getRuntime: () => 1000000000,
stdout: () => { },
pass: jest.fn(),
fail: jest.fn()
}
}