Skip to content
This repository has been archived by the owner on Sep 16, 2021. It is now read-only.

feat: endpoint to post xunit test data #254

Merged
merged 22 commits into from
Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4c857e7
initial xunit-parser work
cedpeters Aug 18, 2020
7b224b5
get rid of pass and fail arrays; just have one array of tests
cedpeters Aug 18, 2020
2da2708
xunit-parser returns an array of testrun objects
cedpeters Aug 18, 2020
4053c91
endpoint code almost done through tests
cedpeters Aug 18, 2020
b4a276b
Merge branch 'master' into endpoint
cedpeters Aug 18, 2020
daa47e6
add second test which checks values in tests
cedpeters Aug 18, 2020
b9b1641
Merge branch 'endpoint' of github.com:GoogleCloudPlatform/flaky-servi…
cedpeters Aug 18, 2020
681c37d
test parsing failed test, name test full path minus github link
cedpeters Aug 18, 2020
b949655
linting
cedpeters Aug 18, 2020
5a07801
remove count var from testrun.js
cedpeters Aug 19, 2020
70fd1c4
const to var
cedpeters Aug 19, 2020
9631fe2
stub out the build parsing
cedpeters Aug 20, 2020
caf8468
two tests for new post-build endpoint
cedpeters Aug 20, 2020
6c19faa
Merge branch 'master' into endpoint
cedpeters Aug 20, 2020
1530917
delete commented out code
cedpeters Aug 20, 2020
376e68b
Merge branch 'endpoint' of github.com:GoogleCloudPlatform/flaky-servi…
cedpeters Aug 20, 2020
694afe6
one last commented out line deleted
cedpeters Aug 20, 2020
8c30ded
switch test name parser to use regex
cedpeters Aug 20, 2020
def620c
split line into shorter ones
cedpeters Aug 21, 2020
469d254
add comments
cedpeters Aug 21, 2020
9203c6e
replace stubs array with sinon.sandbox
cedpeters Aug 21, 2020
d952a42
refactor add-build so that it can more easily be tested
cedpeters Aug 21, 2020
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
88 changes: 88 additions & 0 deletions packages/api/lib/xunit-parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const xmljs = require('xml-js');
const TestCaseRun = require('../lib/testrun');

const parse = (xmlString) => {
let count = 1;
const obj = xmljs.xml2js(xmlString, { compact: true });
const tests = [];
// Python doesn't always have a top-level testsuites element.
let testsuites = obj.testsuite;
if (testsuites === undefined) {
testsuites = obj.testsuites.testsuite;
}
if (testsuites === undefined) {
return tests;
}
// If there is only one test suite, put it into an array to make it iterable.
if (!Array.isArray(testsuites)) {
testsuites = [testsuites];
}
for (const suite of testsuites) {
// Ruby doesn't always have _attributes.
let testsuiteName = suite._attributes ? suite._attributes.name : undefined;

// Get rid of github.com/orgName/repoName/
testsuiteName = testsuiteName.substring(testsuiteName.indexOf('/') + 1);
testsuiteName = testsuiteName.substring(testsuiteName.indexOf('/') + 1);
const index = testsuiteName.indexOf('/');
if (index !== -1) { testsuiteName = testsuiteName.substring(index + 1); } else {
// There is nothing past the repo url
testsuiteName = '';
}

let testcases = suite.testcase;
// If there were no tests in the package, continue.
if (testcases === undefined) {
continue;
}
// If there is only one test case, put it into an array to make it iterable.
if (!Array.isArray(testcases)) {
testcases = [testcases];
}

for (const testcase of testcases) {
cedpeters marked this conversation as resolved.
Show resolved Hide resolved
// Ignore skipped tests. They didn't pass and they didn't fail.
if (testcase.skipped !== undefined) {
continue;
}

const failure = testcase.failure;
const error = testcase.error;

const testCaseRun = new TestCaseRun((failure === undefined && error === undefined) ? 'ok' : 'not ok', count, (testsuiteName !== '') ? testsuiteName + '/' + testcase._attributes.name : testcase._attributes.name);
count++;
cedpeters marked this conversation as resolved.
Show resolved Hide resolved

if (!testCaseRun.successful) {
// Here we must have a failure or an error.
let log = (failure === undefined) ? error._text : failure._text;
// Java puts its test logs in a CDATA element.
if (log === undefined) {
log = failure._cdata;
}

testCaseRun.failureMessage = log;
}

tests.push(testCaseRun);
}
}
return tests;
};

// console.log(parse(readFileSync(require.resolve('../test/fixtures/one_failed.xml'), 'utf8')));

module.exports = parse;
3 changes: 2 additions & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"express-session": "^1.17.1",
"moment": "^2.27.0",
"tap-parser": "^10.0.1",
"uuid": "^8.2.0"
"uuid": "^8.2.0",
"xml-js": "^1.6.11"
},
"devDependencies": {
"c8": "^7.1.2",
Expand Down
33 changes: 31 additions & 2 deletions packages/api/src/post-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@

const addBuild = require('../src/add-build');
const TestCaseRun = require('../lib/testrun');
var Parser = require('tap-parser');
var TapParser = require('tap-parser');
cedpeters marked this conversation as resolved.
Show resolved Hide resolved
const xunitParser = require('../lib/xunit-parser');
const Readable = require('stream').Readable;
const firebaseEncode = require('../lib/firebase-encode');
const { InvalidParameterError, UnauthorizedError, handleError } = require('../lib/errors');
Expand Down Expand Up @@ -121,7 +122,7 @@ class PostBuildHandler {
switch (fileType) {
case 'TAP': {
var data = [];
var p = new Parser();
var p = new TapParser();
cedpeters marked this conversation as resolved.
Show resolved Hide resolved

p.on('result', function (assert) {
data.push(assert);
Expand Down Expand Up @@ -264,6 +265,34 @@ class PostBuildHandler {
handleError(res, err);
}
});

// endpoint expects the the required buildinfo to be in req.body.metadata to already exist and be properly formatted.
// required keys in the req.body.metadata are the inputs for addBuild in src/add-build.js
this.app.post('/api/build/xml', async (req, res, next) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we get which repo or build this XML is for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will be sent in the metadata. I wrote a rough draft of how the data will be sent, if we do use buildcop: https://github.com/cedpeters/repo-automation-bots/pull/1/files

try {
// TODO: Check for private repo, reject if it is one.
// if (req.body.metadata.private) {
// throw new UnauthorizedError('Flaky does not store tests for private repos');
// }

// TODO: Metadata stuff
// const buildInfo = PostBuildHandler.cleanBuildInfo(req.body.metadata); // Different line. The metadata object is the same as addbuild, already validated
const testCases = xunitParser(req.body.data);

const buildInfo = [];

// TODO: Data validation
// const isValid = await validateGithub(req.body.metadata.token, decodeURIComponent(req.body.metadata.repoId));
// if (!isValid) {
// throw new UnauthorizedError('Must have valid Github Token to post build');
// }

await addBuild(PostBuildHandler.removeDuplicateTestCases(testCases), buildInfo, this.client, global.headCollection);
res.send({ message: 'successfully added build' });
} catch (err) {
handleError(res, err);
}
});
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/api/test/addbuild-getbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const buildInfo = [
buildInfo[2].testCases[0].failureMessage = 'Error message';
buildInfo[2].testCases[1].failureMessage = 'Error message';

describe.only('Add-Build', () => {
describe('Add-Build', () => {
before(async () => {
global.headCollection = 'testing/' + TESTING_COLLECTION_BASE + uuidv4() + '/repos'; // random collection name for concurrent testing
});
Expand Down
2 changes: 2 additions & 0 deletions packages/api/test/fixtures/empty_results.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites></testsuites>
29 changes: 29 additions & 0 deletions packages/api/test/fixtures/go_failure_group.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite tests="18" failures="18" time="4.364" name="github.com/GoogleCloudPlatform/golang-samples/bigquery/snippets/querying">
<properties>
<property name="go.version" value="go1.13.1"></property>
</properties>
<testcase classname="querying" name="TestQueries" time="0.000">
<failure message="Failed" type=""></failure>
</testcase>
<testcase classname="querying" name="TestQueries/group" time="0.000">
<failure message="Failed" type=""></failure>
</testcase>
<testcase classname="querying" name="TestQueries/group/queryBasic" time="0.000">
<failure message="Failed" type=""></failure>
</testcase>
<testcase classname="querying" name="TestQueries/group/queryBatch" time="0.000">
<failure message="Failed" type="">panic: runtime error: invalid memory address or nil pointer dereference&#xA;/usr/local/go/src/testing/testing.go:874 +0x3a3&#xA;/usr/local/go/src/runtime/panic.go:679 +0x1b2&#xA;/go/pkg/mod/cloud.google.com/go/bigquery@v1.3.0/iterator.go:106 +0x37&#xA;/tmpfs/src/github/golang-samples/bigquery/snippets/querying/bigquery_query_legacy_large_results.go:59 +0x419&#xA;/tmpfs/src/github/golang-samples/bigquery/snippets/querying/integration_test.go:90 +0xa5&#xA;/usr/local/go/src/testing/testing.go:909 +0xc9&#xA;/usr/local/go/src/testing/testing.go:960 +0x350</failure>
</testcase>
<testcase classname="querying" name="TestQueries/group/queryDisableCache" time="0.000">
<failure message="Failed" type=""></failure>
</testcase>
<testcase classname="querying" name="TestQueries/group/queryDryRun" time="0.000">
<failure message="Failed" type=""></failure>
</testcase>
<testcase classname="querying" name="TestQueries/group/queryLegacy" time="0.000">
<failure message="Failed" type=""></failure>
</testcase>
</testsuite>
</testsuites>
17 changes: 17 additions & 0 deletions packages/api/test/fixtures/go_skip.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite tests="3" failures="0" time="27.297" name="github.com/GoogleCloudPlatform/golang-samples">
<properties>
<property name="go.version" value="go1.11.13"></property>
</properties>
<testcase classname="golang-samples" name="TestLicense" time="0.120"></testcase>
</testsuite>
<testsuite tests="2" failures="0" time="0.526" name="github.com/GoogleCloudPlatform/golang-samples/securitycenter/notifications">
<testcase classname="notifications" name="TestGetNotificationConfig" time="0.000">
<skipped message="notifications_test.go:162: https://github.com/GoogleCloudPlatform/golang-samples/issues/1354"></skipped>
</testcase>
<testcase classname="notifications" name="TestListNotificationConfigs" time="0.000">
<skipped message="notifications_test.go:186: https://github.com/GoogleCloudPlatform/golang-samples/issues/1355"></skipped>
</testcase>
</testsuite>
</testsuites>
Loading