import GLib from 'gi://GLib'; import * as JUnitReporter from '../src/junitReporter.js'; import * as XMLWriter from '../src/xmlWriter.js'; const SUITE_INFO = { id: 'foo', description: 'A suite', fullName: 'A suite', failedExpectations: [], status: 'finished', }; const NESTED_SUITE_INFO = { id: 'baz', description: 'nested', fullName: 'A suite nested', failedExpectations: [], status: 'finished', }; const FAILED_SUITE_INFO = { id: 'cheers', description: 'A failing suite', fullName: 'A failing suite', failedExpectations: [{ matcherName: '', message: 'Some error', stack: 'file.js:113\nfile.js:72\nfile.js:17\n', }], status: 'failed', }; const PASSING_SPEC_INFO = { id: 'bar', description: 'passes a test', fullName: 'A suite passes a test', failedExpectations: [], passedExpectations: [ { matcherName: 'toBe', message: 'Expected true to be true', }, { matcherName: 'toContain', message: 'Expected [1] to contain 1', }, ], status: 'passed', }; const NESTED_PASSING_SPEC_INFO = { id: 'boz', description: 'passes a test', fullName: 'A suite nested passes a test', failedExpectations: [], passedExpectations: [], status: 'passed', }; const PENDING_SPEC_INFO = { id: 'skip', description: 'skips a test', fullName: 'A suite skips a test', failedExpectations: [], passedExpectations: [], status: 'pending', }; const FAILING_SPEC_INFO = { id: 'bad', description: 'fails a test', fullName: 'A suite fails a test', failedExpectations: [{ matcherName: 'toBe', message: 'Expected true to be false', stack: 'file.js:113\nfile.js:72\nfile.js:17\n', }], passedExpectations: [], status: 'failed', }; const ERROR_SPEC_INFO = { id: 'bug', description: 'has a bug in a test', fullName: 'A suite has a bug in a test', failedExpectations: [ { matcherName: '', message: 'TypeError: foo is not a function', stack: 'file.js:113\nfile.js:72\nfile.js:17\n', }, { matcherName: '', message: 'Some other unknown error', stack: 'file.js:113\nfile.js:72\nfile.js:17\n', }, ], passedExpectations: [], status: 'failed', }; const DISABLED_SPEC_INFO = { id: 'wut', description: 'disables a test', fullName: 'A suite disables a test', failedExpectations: [], passedExpectations: [], status: 'disabled', }; describe('The JUnit reporter', function () { let out, reporter, timerSpies; beforeEach(function () { // Override the XML outputting function to output JSON instead. This is // for ease of verifying the output. XML is inconvenient to parse in the // DOM-less GJS. Any regressions in the XML output should not be tested // here, but instead should be covered in xmlWriterSpec.js. spyOn(XMLWriter.Node.prototype, 'toString').and.callFake(function () { return JSON.stringify(this); }); out = (function () { let output = ''; return { print(str) { output += str; }, getOutput() { return output; }, clear() { output = ''; }, }; })(); timerSpies = {}; const timerSpy = id => { timerSpies[id] = jasmine.createSpyObj('timer', ['start', 'elapsed']); return timerSpies[id]; }; reporter = new JUnitReporter.JUnitReporter({ print: out.print, timerFactory: timerSpy, }); reporter.jasmineStarted(); }); function runSpec(specInfo) { reporter.specStarted(specInfo); reporter.specDone(specInfo); } function runSuite(suiteInfo, specs) { reporter.suiteStarted(suiteInfo); specs.forEach(runSpec); reporter.suiteDone(suiteInfo); } // Find the element with the given ID inside the // element given by tree. This is necessary because other elements may be // present such as, , so we cannot rely on the element with ID 0 // being the first child of . function findSuite(tree, id) { for (let index = 0; index < tree.children.length; index++) { const child = tree.children[index]; if (child.name === 'testsuite' && child.attrs['id'] === id) return child; } return undefined; } // For XML builder reasons, the report is only output at the end of all the // test suites. Therefore all tests must call jasmineDone(). it('outputs a JUnit report', function () { reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); expect(tree.name).toBe('testsuites'); }); it('reports all required elements of a test suite', function () { runSuite(SUITE_INFO, []); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(testsuite.name).toBe('testsuite'); expect(testsuite.attrs['name']).toBe('A suite'); expect(testsuite.attrs['tests']).toBe(0); }); it('reports all required elements of a test case element', function () { runSuite(SUITE_INFO, [PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); const [{name, attrs}] = testsuite.children; expect(name).toBe('testcase'); expect(attrs['name']).toBe('passes a test'); expect(attrs['classname']).toBe('A suite'); }); it('reports a pending spec as skipped', function () { runSuite(SUITE_INFO, [PENDING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); const [{children}] = testsuite.children; expect(children[0].name).toBe('skipped'); }); describe('given a spec with a failed expectation', function () { let failure; beforeEach(function () { runSuite(SUITE_INFO, [FAILING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); [failure] = testsuite.children[0].children; }); it('reports it as failed', function () { expect(failure.name).toBe('failure'); }); it('reports the matcher name as the failure type', function () { if (!failure.attrs.hasOwnProperty('type')) pending(); expect(failure.attrs['type']).toBe('toBe'); }); it('reports the expectation message', function () { if (!failure.attrs.hasOwnProperty('message')) pending(); expect(failure.attrs['message']).toBe('Expected true to be false'); }); it('reports the stack trace', function () { expect(failure.text).toBe('file.js:113\nfile.js:72\nfile.js:17\n'); }); }); describe('given a spec with an uncaught exception', function () { let error1, error2; beforeEach(function () { runSuite(SUITE_INFO, [ERROR_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); [error1, error2] = testsuite.children[0].children; }); it('reports it as errored', function () { expect(error1.name).toBe('error'); expect(error2.name).toBe('error'); }); it('reports the exception class as the failure type', function () { expect(error1.attrs['type']).toBe('TypeError'); }); it('picks a default type if the exception class is not known', function () { expect(error2.attrs['type']).toBe('Error'); }); it('reports the error message', function () { expect(error1.attrs['message']).toBe('TypeError: foo is not a function'); expect(error2.attrs['message']).toBe('Some other unknown error'); }); it('reports the stack trace', function () { expect(error1.text).toBe('file.js:113\nfile.js:72\nfile.js:17\n'); expect(error2.text).toBe('file.js:113\nfile.js:72\nfile.js:17\n'); }); }); it('does not report a disabled spec', function () { runSuite(SUITE_INFO, [DISABLED_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(testsuite.children.length).toBe(0); }); it('gives each suite an increasing ID number', function () { runSuite(SUITE_INFO, [PASSING_SPEC_INFO]); runSuite(SUITE_INFO, [PASSING_SPEC_INFO]); runSuite(SUITE_INFO, [PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); tree.children.filter(child => child.name === 'testsuite') .forEach((child, index) => { expect(child.attrs['id']).toBe(index); }); }); it('times all suites together', function () { timerSpies['main'].elapsed.and.returnValue(1200); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); expect(tree.attrs['time']).toBeCloseTo(1.2, 4); }); it('times individual suites', function () { reporter.suiteStarted(SUITE_INFO); timerSpies[`suite:${SUITE_INFO.id}`].elapsed.and.returnValue(100); reporter.suiteDone(SUITE_INFO); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(testsuite.attrs['time']).toBeCloseTo(0.1, 4); }); it('times individual specs', function () { reporter.suiteStarted(SUITE_INFO); reporter.specStarted(PASSING_SPEC_INFO); timerSpies[`spec:${PASSING_SPEC_INFO.id}`].elapsed.and.returnValue(100); reporter.specDone(PASSING_SPEC_INFO); reporter.suiteDone(SUITE_INFO); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); const [{attrs}] = testsuite.children; expect(attrs['time']).toBeCloseTo(0.1, 4); }); it('counts all tests in a suite', function () { runSuite(SUITE_INFO, [PASSING_SPEC_INFO, PASSING_SPEC_INFO, PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(testsuite.attrs['tests']).toBe(3); }); it('counts disabled tests in a suite', function () { runSuite(SUITE_INFO, [PASSING_SPEC_INFO, DISABLED_SPEC_INFO, PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(testsuite.attrs['disabled']).toBe(1); }); it('counts errored tests in a suite', function () { runSuite(SUITE_INFO, [PASSING_SPEC_INFO, ERROR_SPEC_INFO, PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(testsuite.attrs['errors']).toBe(1); }); it('counts failed tests in a suite', function () { runSuite(SUITE_INFO, [PASSING_SPEC_INFO, FAILING_SPEC_INFO, PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(testsuite.attrs['failures']).toBe(1); }); it('counts skipped tests in a suite', function () { runSuite(SUITE_INFO, [PASSING_SPEC_INFO, PENDING_SPEC_INFO, PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(testsuite.attrs['skipped']).toBe(1); }); it('timestamps a suite', function () { runSuite(SUITE_INFO, []); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(() => Date.parse(testsuite.attrs['timestamp'])).not.toThrow(); }); it('flattens nested suites', function () { reporter.suiteStarted(SUITE_INFO); [PASSING_SPEC_INFO, PASSING_SPEC_INFO].forEach(runSpec); reporter.suiteStarted(NESTED_SUITE_INFO); [NESTED_PASSING_SPEC_INFO, NESTED_PASSING_SPEC_INFO].forEach(runSpec); reporter.suiteDone(NESTED_SUITE_INFO); runSpec(PASSING_SPEC_INFO); reporter.suiteDone(SUITE_INFO); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const suite1 = findSuite(tree, 0); const suite2 = findSuite(tree, 1); expect(suite1.attrs['tests']).toBe(3); expect(suite2.attrs['tests']).toBe(2); }); it('reports exceptions in afterAll() as errors in a separate suite', function () { runSuite(FAILED_SUITE_INFO, [PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const afterAllSuite = findSuite(tree, 1); expect(afterAllSuite.attrs['tests']).toBe(1); expect(afterAllSuite.attrs['errors']).toBe(1); }); it('reports an error in afterAll() as a test case', function () { runSuite(FAILED_SUITE_INFO, [PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const afterAllSuite = findSuite(tree, 1); expect(afterAllSuite.children[0]).toEqual(jasmine.objectContaining({ name: 'testcase', attrs: jasmine.any(Object), children: [jasmine.objectContaining({ name: 'error', attrs: jasmine.objectContaining({ message: 'Some error', type: 'Error', }), text: 'file.js:113\nfile.js:72\nfile.js:17\n', })], })); }); it('adds the environment in a element', function () { GLib.setenv('JASMINE_TESTS_BOGUS_VARIABLE', 'surprise', true); reporter.jasmineStarted(); // restart reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const [properties] = tree.children.filter(child => child.name === 'properties'); expect(properties.children).toContain(jasmine.objectContaining({ name: 'property', attrs: { name: 'JASMINE_TESTS_BOGUS_VARIABLE', value: 'surprise', }, })); }); it('reports the total number of assertions', function () { runSuite(SUITE_INFO, [PASSING_SPEC_INFO, ERROR_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const testsuite = findSuite(tree, 0); expect(testsuite.children[0].attrs['assertions']).toBe(2); expect(testsuite.children[1].attrs['assertions']).toBe(2); }); it('reports the total number of assertions in an afterAll() suite', function () { runSuite(FAILED_SUITE_INFO, [PASSING_SPEC_INFO]); reporter.jasmineDone(); const tree = JSON.parse(out.getOutput()); const afterAllSuite = findSuite(tree, 1); expect(afterAllSuite.children[0].attrs['assertions']).toBe(1); }); });