[7.x] refactor failed_tests_reporter to use TS, no octokit (#4… (#47631)
* refactor failed_tests_reporter to use TS, no octokit * update renovate config * ensure that all kbn-test files are in ts project * fix some type errors * add some more tests * [kbn-test/githubapi] cleanup and document * collect log messages as strings instead of message objects * ensure issue is open when updating body * improve readability of getKibanaIssues * expose axios helpers from dev-utils * fix request params for fetching github issues and validate locally * include a README for failed_tests_reporter * improve axios error helpers # Conflicts: # packages/kbn-test/src/index.ts # renovate.json5
This commit is contained in:
parent
95f482d80c
commit
3fadf26699
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
|
@ -294,6 +294,6 @@ def buildXpack() {
|
||||||
def runErrorReporter() {
|
def runErrorReporter() {
|
||||||
bash """
|
bash """
|
||||||
source src/dev/ci_setup/setup_env.sh
|
source src/dev/ci_setup/setup_env.sh
|
||||||
node src/dev/failed_tests/cli
|
node scripts/report_failed_tests
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
|
@ -280,7 +280,6 @@
|
||||||
"@kbn/utility-types": "1.0.0",
|
"@kbn/utility-types": "1.0.0",
|
||||||
"@microsoft/api-documenter": "7.4.3",
|
"@microsoft/api-documenter": "7.4.3",
|
||||||
"@microsoft/api-extractor": "7.4.2",
|
"@microsoft/api-extractor": "7.4.2",
|
||||||
"@octokit/rest": "^15.10.0",
|
|
||||||
"@percy/agent": "^0.11.0",
|
"@percy/agent": "^0.11.0",
|
||||||
"@types/angular": "^1.6.56",
|
"@types/angular": "^1.6.56",
|
||||||
"@types/angular-mocks": "^1.7.0",
|
"@types/angular-mocks": "^1.7.0",
|
||||||
|
|
|
@ -28,15 +28,9 @@ export interface AxiosResponseError<T> extends AxiosError {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isAxiosRequestError = (error: any): error is AxiosRequestError => {
|
export const isAxiosRequestError = (error: any): error is AxiosRequestError => {
|
||||||
return error && error.code === undefined && error.response === undefined;
|
return error && error.config && error.response === undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isAxiosResponseError = (error: any): error is AxiosResponseError<any> => {
|
export const isAxiosResponseError = <T = any>(error: any): error is AxiosResponseError<T> => {
|
||||||
return error && error.code !== undefined && error.response !== undefined;
|
return error && error.response && error.response.status !== undefined;
|
||||||
};
|
|
||||||
|
|
||||||
export const isConcliftOnGetError = (error: any) => {
|
|
||||||
return (
|
|
||||||
isAxiosResponseError(error) && error.config.method === 'GET' && error.response.status === 409
|
|
||||||
);
|
|
||||||
};
|
};
|
20
packages/kbn-dev-utils/src/axios/index.ts
Normal file
20
packages/kbn-dev-utils/src/axios/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './errors';
|
|
@ -18,9 +18,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { withProcRunner } from './proc_runner';
|
export { withProcRunner } from './proc_runner';
|
||||||
export { ToolingLog, ToolingLogTextWriter, pickLevelFromFlags } from './tooling_log';
|
export {
|
||||||
|
ToolingLog,
|
||||||
|
ToolingLogTextWriter,
|
||||||
|
pickLevelFromFlags,
|
||||||
|
ToolingLogCollectingWriter,
|
||||||
|
} from './tooling_log';
|
||||||
export { createAbsolutePathSerializer } from './serializers';
|
export { createAbsolutePathSerializer } from './serializers';
|
||||||
export { CA_CERT_PATH, ES_KEY_PATH, ES_CERT_PATH } from './certs';
|
export { CA_CERT_PATH, ES_KEY_PATH, ES_CERT_PATH } from './certs';
|
||||||
export { run, createFailError, createFlagError, combineErrors, isFailError, Flags } from './run';
|
export { run, createFailError, createFlagError, combineErrors, isFailError, Flags } from './run';
|
||||||
export { REPO_ROOT } from './constants';
|
export { REPO_ROOT } from './constants';
|
||||||
export { KbnClient } from './kbn_client';
|
export { KbnClient } from './kbn_client';
|
||||||
|
export * from './axios';
|
||||||
|
|
|
@ -21,9 +21,15 @@ import Url from 'url';
|
||||||
|
|
||||||
import Axios from 'axios';
|
import Axios from 'axios';
|
||||||
|
|
||||||
import { isAxiosRequestError, isConcliftOnGetError } from './errors';
|
import { isAxiosRequestError, isAxiosResponseError } from '../axios';
|
||||||
import { ToolingLog } from '../tooling_log';
|
import { ToolingLog } from '../tooling_log';
|
||||||
|
|
||||||
|
const isConcliftOnGetError = (error: any) => {
|
||||||
|
return (
|
||||||
|
isAxiosResponseError(error) && error.config.method === 'GET' && error.response.status === 409
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const uriencode = (
|
export const uriencode = (
|
||||||
strings: TemplateStringsArray,
|
strings: TemplateStringsArray,
|
||||||
...values: Array<string | number | boolean>
|
...values: Array<string | number | boolean>
|
||||||
|
|
|
@ -20,3 +20,4 @@
|
||||||
export { ToolingLog } from './tooling_log';
|
export { ToolingLog } from './tooling_log';
|
||||||
export { ToolingLogTextWriter, ToolingLogTextWriterConfig } from './tooling_log_text_writer';
|
export { ToolingLogTextWriter, ToolingLogTextWriterConfig } from './tooling_log_text_writer';
|
||||||
export { pickLevelFromFlags, LogLevel } from './log_levels';
|
export { pickLevelFromFlags, LogLevel } from './log_levels';
|
||||||
|
export { ToolingLogCollectingWriter } from './tooling_log_collecting_writer';
|
||||||
|
|
|
@ -17,27 +17,20 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Octokit from '@octokit/rest';
|
import { ToolingLogTextWriter } from './tooling_log_text_writer';
|
||||||
import { markdownMetadata } from './metadata';
|
|
||||||
|
|
||||||
export { markdownMetadata };
|
export class ToolingLogCollectingWriter extends ToolingLogTextWriter {
|
||||||
|
messages: string[] = [];
|
||||||
|
|
||||||
export function getGithubClient() {
|
constructor() {
|
||||||
const client = new Octokit();
|
super({
|
||||||
client.authenticate({
|
level: 'verbose',
|
||||||
type: 'token',
|
writeTo: {
|
||||||
token: process.env.GITHUB_TOKEN
|
write: msg => {
|
||||||
|
// trim trailing new line
|
||||||
|
this.messages.push(msg.slice(0, -1));
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function paginate(client, promise) {
|
|
||||||
let response = await promise;
|
|
||||||
let { data } = response;
|
|
||||||
while (client.hasNextPage(response)) {
|
|
||||||
response = await client.getNextPage(response);
|
|
||||||
data = data.concat(response.data);
|
|
||||||
}
|
}
|
||||||
return data;
|
|
||||||
}
|
}
|
|
@ -6,5 +6,5 @@
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,10 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.5.5",
|
"@babel/cli": "^7.5.5",
|
||||||
"@kbn/babel-preset": "1.0.0",
|
"@kbn/babel-preset": "1.0.0",
|
||||||
"@kbn/dev-utils": "1.0.0"
|
"@kbn/dev-utils": "1.0.0",
|
||||||
|
"@types/parse-link-header": "^1.0.0",
|
||||||
|
"@types/strip-ansi": "^5.2.1",
|
||||||
|
"@types/xml2js": "^0.4.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^2.4.2",
|
"chalk": "^2.4.2",
|
||||||
|
@ -20,9 +23,12 @@
|
||||||
"del": "^4.1.1",
|
"del": "^4.1.1",
|
||||||
"getopts": "^2.2.4",
|
"getopts": "^2.2.4",
|
||||||
"glob": "^7.1.2",
|
"glob": "^7.1.2",
|
||||||
|
"parse-link-header": "^1.0.1",
|
||||||
|
"strip-ansi": "^5.2.0",
|
||||||
"rxjs": "^6.2.1",
|
"rxjs": "^6.2.1",
|
||||||
"tar-fs": "^1.16.3",
|
"tar-fs": "^1.16.3",
|
||||||
"tmp": "^0.1.0",
|
"tmp": "^0.1.0",
|
||||||
|
"xml2js": "^0.4.22",
|
||||||
"zlib": "^1.0.5"
|
"zlib": "^1.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
21
packages/kbn-test/src/failed_tests_reporter/README.md
Normal file
21
packages/kbn-test/src/failed_tests_reporter/README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# failed tests reporter
|
||||||
|
|
||||||
|
A little CLI that runs in CI to find the failed tests in the JUnit reports, then create/update github issues for each failure.
|
||||||
|
|
||||||
|
## Test this script locally
|
||||||
|
|
||||||
|
To fetch some JUnit reports from a recent build on CI, visit its `Google Cloud Storage Upload Report` and execute the following in the JS Console:
|
||||||
|
|
||||||
|
```js
|
||||||
|
copy(`wget "${Array.from($$('a[href$=".xml"]')).filter(a => a.innerText === 'Download').map(a => a.href.replace('https://storage.cloud.google.com/', 'https://storage.googleapis.com/')).join('" "')}"`)
|
||||||
|
```
|
||||||
|
|
||||||
|
This copies a script to download the reporets, which can be executed in the `test/junit` directory.
|
||||||
|
|
||||||
|
Next, run the CLI in `--dry-run` mode so that it doesn't actually communicate with Github.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
node scripts/report_failed_tests.js --verbose --dry-run --build-url foo
|
||||||
|
```
|
||||||
|
|
||||||
|
If you specify the `GITHUB_TOKEN` environment variable then `--dry-run` will execute read operations but still won't execute write operations.
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ToolingLog } from '@kbn/dev-utils';
|
||||||
|
|
||||||
|
import { getFailures } from './get_failures';
|
||||||
|
|
||||||
|
const log = new ToolingLog();
|
||||||
|
|
||||||
|
it('discovers failures in ftr report', async () => {
|
||||||
|
const failures = await getFailures(log, require.resolve('./__fixtures__/ftr_report.xml'));
|
||||||
|
expect(failures).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"classname": "Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js",
|
||||||
|
"failure": "
|
||||||
|
Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj~=\\"layerTocActionsPanelToggleButtonRoad_Map_-_Bright\\"])
|
||||||
|
Wait timed out after 10055ms
|
||||||
|
at /var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:834:17
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:68:7)
|
||||||
|
at lastError (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:28:9)
|
||||||
|
at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13)
|
||||||
|
",
|
||||||
|
"name": "maps app maps loaded from sample data ecommerce \\"before all\\" hook",
|
||||||
|
"time": "154.378",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('discovers failures in jest report', async () => {
|
||||||
|
const failures = await getFailures(log, require.resolve('./__fixtures__/jest_report.xml'));
|
||||||
|
expect(failures).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"classname": "X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp",
|
||||||
|
"failure": "
|
||||||
|
TypeError: Cannot read property '0' of undefined
|
||||||
|
at Object.<anonymous>.test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10)
|
||||||
|
",
|
||||||
|
"name": "launcher can reconnect if process died",
|
||||||
|
"time": "7.060",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('discovers failures in karma report', async () => {
|
||||||
|
const failures = await getFailures(log, require.resolve('./__fixtures__/karma_report.xml'));
|
||||||
|
expect(failures).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"classname": "Browser Unit Tests.CoordinateMapsVisualizationTest",
|
||||||
|
"failure": "Error: expected 7069 to be below 64
|
||||||
|
at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11)
|
||||||
|
at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8)
|
||||||
|
at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15)
|
||||||
|
at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60)
|
||||||
|
at tryCatch (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:62:40)
|
||||||
|
at Generator.invoke [as _invoke] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:288:22)
|
||||||
|
at Generator.prototype.<computed> [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21)
|
||||||
|
at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103)
|
||||||
|
at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194)
|
||||||
|
",
|
||||||
|
"name": "CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK",
|
||||||
|
"time": "0.265",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('discovers failures in mocha report', async () => {
|
||||||
|
const failures = await getFailures(log, require.resolve('./__fixtures__/mocha_report.xml'));
|
||||||
|
expect(failures).toMatchInlineSnapshot(`Array []`);
|
||||||
|
});
|
163
packages/kbn-test/src/failed_tests_reporter/get_failures.ts
Normal file
163
packages/kbn-test/src/failed_tests_reporter/get_failures.ts
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { promisify } from 'util';
|
||||||
|
import Fs from 'fs';
|
||||||
|
|
||||||
|
import xml2js from 'xml2js';
|
||||||
|
import stripAnsi from 'strip-ansi';
|
||||||
|
import { ToolingLog } from '@kbn/dev-utils';
|
||||||
|
|
||||||
|
type TestReport =
|
||||||
|
| {
|
||||||
|
testsuites: {
|
||||||
|
testsuite: TestSuite[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
testsuite: TestSuite;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TestSuite {
|
||||||
|
$: {
|
||||||
|
/* ISO8601 timetamp when test suite ran */
|
||||||
|
timestamp: string;
|
||||||
|
/* number of second this tests suite took */
|
||||||
|
time: string;
|
||||||
|
/* number of tests as a string */
|
||||||
|
tests: string;
|
||||||
|
/* number of failed tests as a string */
|
||||||
|
failures: string;
|
||||||
|
/* number of skipped tests as a string */
|
||||||
|
skipped: string;
|
||||||
|
};
|
||||||
|
testcase: TestCase[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestCase {
|
||||||
|
$: {
|
||||||
|
/* unique test name */
|
||||||
|
name: string;
|
||||||
|
/* somewhat human readable combination of test name and file */
|
||||||
|
classname: string;
|
||||||
|
/* number of seconds this test took */
|
||||||
|
time: string;
|
||||||
|
};
|
||||||
|
/* contents of system-out elements */
|
||||||
|
'system-out'?: string[];
|
||||||
|
/* contents of failure elements */
|
||||||
|
failure?: Array<string | { _: string }>;
|
||||||
|
/* contents of skipped elements */
|
||||||
|
skipped?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TestFailure = TestCase['$'] & {
|
||||||
|
failure: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readAsync = promisify(Fs.readFile);
|
||||||
|
|
||||||
|
const indent = (text: string) =>
|
||||||
|
` ${text
|
||||||
|
.split('\n')
|
||||||
|
.map(l => ` ${l}`)
|
||||||
|
.join('\n')}`;
|
||||||
|
|
||||||
|
const getFailureText = (failure: NonNullable<TestCase['failure']>) => {
|
||||||
|
const [failureNode] = failure;
|
||||||
|
|
||||||
|
if (failureNode && typeof failureNode === 'object' && typeof failureNode._ === 'string') {
|
||||||
|
return stripAnsi(failureNode._);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stripAnsi(String(failureNode));
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLikelyIrrelevant = ({ name, failure }: TestFailure) => {
|
||||||
|
if (
|
||||||
|
failure.includes('NoSuchSessionError: This driver instance does not have a valid session ID')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failure.includes('Error: No Living connections')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
name.includes('"after all" hook') &&
|
||||||
|
failure.includes(`Cannot read property 'shutdown' of undefined`)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
failure.includes('Unable to read artifact info') &&
|
||||||
|
failure.includes('Service Temporarily Unavailable')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failure.includes('Unable to fetch Kibana status API response from Kibana')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getFailures(log: ToolingLog, testReportPath: string) {
|
||||||
|
const xml = await readAsync(testReportPath, 'utf8');
|
||||||
|
|
||||||
|
// Parses junit XML files
|
||||||
|
const report: TestReport = await xml2js.parseStringPromise(xml);
|
||||||
|
|
||||||
|
// Grab the failures. Reporters may report multiple testsuites in a single file.
|
||||||
|
const testSuites = 'testsuites' in report ? report.testsuites.testsuite : [report.testsuite];
|
||||||
|
|
||||||
|
const failures: TestFailure[] = [];
|
||||||
|
for (const testSuite of testSuites) {
|
||||||
|
for (const testCase of testSuite.testcase) {
|
||||||
|
const { failure } = testCase;
|
||||||
|
|
||||||
|
if (!failure) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap xml weirdness
|
||||||
|
const failureCase: TestFailure = {
|
||||||
|
...testCase.$,
|
||||||
|
// Strip ANSI color characters
|
||||||
|
failure: getFailureText(failure),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLikelyIrrelevant(failureCase)) {
|
||||||
|
log.warning(
|
||||||
|
`Ignoring likely irrelevant failure: ${failureCase.classname} - ${
|
||||||
|
failureCase.name
|
||||||
|
}\n${indent(failureCase.failure)}`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
failures.push(failureCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Found ${failures.length} test failures`);
|
||||||
|
|
||||||
|
return failures;
|
||||||
|
}
|
169
packages/kbn-test/src/failed_tests_reporter/github_api.ts
Normal file
169
packages/kbn-test/src/failed_tests_reporter/github_api.ts
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Url from 'url';
|
||||||
|
|
||||||
|
import Axios, { AxiosRequestConfig } from 'axios';
|
||||||
|
import parseLinkHeader from 'parse-link-header';
|
||||||
|
import { ToolingLog, isAxiosResponseError } from '@kbn/dev-utils';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://api.github.com/repos/elastic/kibana/';
|
||||||
|
|
||||||
|
export interface GithubIssue {
|
||||||
|
html_url: string;
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
labels: unknown[];
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestOptions = AxiosRequestConfig & { safeForDryRun?: boolean };
|
||||||
|
|
||||||
|
export class GithubApi {
|
||||||
|
private readonly x = Axios.create({
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${this.token}`,
|
||||||
|
'User-Agent': 'elastic/kibana#failed_test_reporter',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a GithubApi helper object, if token is undefined requests won't be
|
||||||
|
* sent, but will instead be logged.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private readonly log: ToolingLog,
|
||||||
|
private readonly token: string | undefined,
|
||||||
|
private readonly dryRun: boolean
|
||||||
|
) {
|
||||||
|
if (!token && !dryRun) {
|
||||||
|
throw new TypeError('token parameter is required');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllFailedTestIssues() {
|
||||||
|
this.log.info('Fetching failed-test issues');
|
||||||
|
const issues: GithubIssue[] = [];
|
||||||
|
let nextRequest: RequestOptions = {
|
||||||
|
safeForDryRun: true,
|
||||||
|
method: 'GET',
|
||||||
|
url: Url.resolve(BASE_URL, 'issues'),
|
||||||
|
params: {
|
||||||
|
state: 'all',
|
||||||
|
per_page: '100',
|
||||||
|
labels: 'failed-test',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const resp = await this.request<GithubIssue[]>(nextRequest, []);
|
||||||
|
|
||||||
|
for (const issue of resp.data) {
|
||||||
|
issues.push(issue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = parseLinkHeader(resp.headers.link);
|
||||||
|
if (parsed && parsed.next && parsed.next.url) {
|
||||||
|
nextRequest = {
|
||||||
|
safeForDryRun: true,
|
||||||
|
method: 'GET',
|
||||||
|
url: parsed.next.url,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
async editIssueBodyAndEnsureOpen(issueNumber: number, newBody: string) {
|
||||||
|
await this.request(
|
||||||
|
{
|
||||||
|
method: 'PATCH',
|
||||||
|
url: Url.resolve(BASE_URL, `issues/${encodeURIComponent(issueNumber)}`),
|
||||||
|
data: {
|
||||||
|
state: 'open', // Reopen issue if it was closed.
|
||||||
|
body: newBody,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addIssueComment(issueNumber: number, commentBody: string) {
|
||||||
|
await this.request(
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
url: Url.resolve(BASE_URL, `issues/${encodeURIComponent(issueNumber)}/comments`),
|
||||||
|
data: {
|
||||||
|
body: commentBody,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createIssue(title: string, body: string, labels?: string[]) {
|
||||||
|
const resp = await this.request(
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
url: Url.resolve(BASE_URL, 'issues'),
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
labels,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html_url: 'https://dryrun',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return resp.data.html_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async request<T>(options: RequestOptions, dryRunResponse: T) {
|
||||||
|
const executeRequest = !this.dryRun || options.safeForDryRun;
|
||||||
|
this.log.verbose('Github API', executeRequest ? 'Request' : 'Dry Run', options);
|
||||||
|
|
||||||
|
if (executeRequest) {
|
||||||
|
try {
|
||||||
|
return await this.x.request<T>(options);
|
||||||
|
} catch (error) {
|
||||||
|
if (isAxiosResponseError(error)) {
|
||||||
|
throw new Error(
|
||||||
|
`[${error.config.method} ${error.config.url}] ${error.response.status} ${
|
||||||
|
error.response.statusText
|
||||||
|
} Error: ${JSON.stringify(error.response.data)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
headers: {},
|
||||||
|
data: dryRunResponse,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
20
packages/kbn-test/src/failed_tests_reporter/index.ts
Normal file
20
packages/kbn-test/src/failed_tests_reporter/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { runFailedTestsReporterCli } from './run_failed_tests_reporter_cli';
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import dedent from 'dedent';
|
||||||
|
|
||||||
|
import { getIssueMetadata, updateIssueMetadata } from './issue_metadata';
|
||||||
|
|
||||||
|
const HAS_METADATA = dedent`
|
||||||
|
# my issue
|
||||||
|
|
||||||
|
some text
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {"failed-test": {"foo": "bar"}} -->
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HAS_SOME_OTHER_METADATA = dedent`
|
||||||
|
# my issue
|
||||||
|
|
||||||
|
some text
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {"some-other": {"foo": "bar"}} -->
|
||||||
|
`;
|
||||||
|
|
||||||
|
const INVALID_METADATA = dedent`
|
||||||
|
# my issue
|
||||||
|
|
||||||
|
some text
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {"failed-test" -->
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MISSING_METADATA = dedent`
|
||||||
|
# my issue
|
||||||
|
|
||||||
|
some text
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe('getIssueMetadata', () => {
|
||||||
|
it('reads properly formatted metadata', () => {
|
||||||
|
expect(getIssueMetadata(HAS_METADATA, 'foo')).toBe('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined if JSON is malformed', () => {
|
||||||
|
expect(getIssueMetadata(INVALID_METADATA, 'foo')).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined if metadata is missing', () => {
|
||||||
|
expect(getIssueMetadata(MISSING_METADATA, 'foo')).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined if JSON is missing `failed-test` property', () => {
|
||||||
|
expect(getIssueMetadata(HAS_SOME_OTHER_METADATA, 'foo')).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns defaultValue if specified', () => {
|
||||||
|
expect(getIssueMetadata(HAS_METADATA, 'foo2', 'bar2')).toBe('bar2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined if defaultValue is not specified', () => {
|
||||||
|
expect(getIssueMetadata(HAS_METADATA, 'foo2')).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateIssueMetadata', () => {
|
||||||
|
it('merges new values with previous values', () => {
|
||||||
|
expect(
|
||||||
|
updateIssueMetadata(HAS_METADATA, {
|
||||||
|
box: 'baz',
|
||||||
|
})
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"# my issue
|
||||||
|
|
||||||
|
some text
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {\\"failed-test\\":{\\"foo\\":\\"bar\\",\\"box\\":\\"baz\\"}} -->"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds metadata if not found', () => {
|
||||||
|
expect(
|
||||||
|
updateIssueMetadata(MISSING_METADATA, {
|
||||||
|
box: 'baz',
|
||||||
|
})
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"# my issue
|
||||||
|
|
||||||
|
some text
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {\\"failed-test\\":{\\"box\\":\\"baz\\"}} -->"
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
updateIssueMetadata(HAS_SOME_OTHER_METADATA, {
|
||||||
|
box: 'baz',
|
||||||
|
})
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"# my issue
|
||||||
|
|
||||||
|
some text
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {\\"some-other\\":{\\"foo\\":\\"bar\\"},\\"failed-test\\":{\\"box\\":\\"baz\\"}} -->"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('overwrites metdata if JSON is malformed', () => {
|
||||||
|
expect(
|
||||||
|
updateIssueMetadata(INVALID_METADATA, {
|
||||||
|
box: 'baz',
|
||||||
|
})
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"# my issue
|
||||||
|
|
||||||
|
some text
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {\\"failed-test\\":{\\"box\\":\\"baz\\"}} -->"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -17,8 +17,6 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const REGEX = /\n\n<!-- kibanaCiData = (.*) -->/;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows retrieving and setting key/value pairs on a Github Issue. Keys and values must be JSON-serializable.
|
* Allows retrieving and setting key/value pairs on a Github Issue. Keys and values must be JSON-serializable.
|
||||||
* Derived from https://github.com/probot/metadata/blob/6ae1523d5035ba727d09c0e7f77a6a154d9a4777/index.js
|
* Derived from https://github.com/probot/metadata/blob/6ae1523d5035ba727d09c0e7f77a6a154d9a4777/index.js
|
||||||
|
@ -47,42 +45,43 @@ const REGEX = /\n\n<!-- kibanaCiData = (.*) -->/;
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
*/
|
*/
|
||||||
export const markdownMetadata = {
|
|
||||||
get(body, key = null, prefix = 'failed-test') {
|
const PREFIX = 'failed-test';
|
||||||
|
const REGEX = /\n\n<!-- kibanaCiData = (.*) -->/;
|
||||||
|
|
||||||
|
function safeJsonParse(json: string, onError: any) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(json);
|
||||||
|
} catch {
|
||||||
|
return onError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse metadata from issue body
|
||||||
|
*/
|
||||||
|
export function getIssueMetadata(body: string, key: string, defaultValue: any = undefined) {
|
||||||
const match = body.match(REGEX);
|
const match = body.match(REGEX);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const data = JSON.parse(match[1])[prefix];
|
const data = safeJsonParse(match[1], {})[PREFIX];
|
||||||
return key ? data && data[key] : data;
|
return data && data[key] !== undefined ? data[key] : defaultValue;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set data on the body. Can either be set individually with `key` and `value` OR
|
* Set data on the body.
|
||||||
*/
|
*/
|
||||||
set(body, key, value, prefix = 'failed-test') {
|
export function updateIssueMetadata(body: string, values: Record<string, any>) {
|
||||||
let newData = {};
|
if (REGEX.test(body)) {
|
||||||
// If second arg is an object, use all supplied values.
|
return body.replace(REGEX, (match, json) => {
|
||||||
if (typeof key === 'object') {
|
const data = safeJsonParse(json, {});
|
||||||
newData = key;
|
data[PREFIX] = Object.assign(data[PREFIX] || {}, values);
|
||||||
prefix = value || prefix; // need to move third arg to prefix.
|
return match.replace(json, JSON.stringify(data));
|
||||||
} else {
|
|
||||||
newData[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = {};
|
|
||||||
|
|
||||||
body = body.replace(REGEX, (_, json) => {
|
|
||||||
data = JSON.parse(json);
|
|
||||||
return '';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!data[prefix]) data[prefix] = {};
|
|
||||||
|
|
||||||
Object.assign(data[prefix], newData);
|
|
||||||
|
|
||||||
return `${body}\n\n<!-- kibanaCiData = ${JSON.stringify(data)} -->`;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
return `${body}\n\n<!-- kibanaCiData = ${JSON.stringify({ [PREFIX]: values })} -->`;
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import dedent from 'dedent';
|
||||||
|
import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/dev-utils';
|
||||||
|
|
||||||
|
import { createFailureIssue, updatedFailureIssue } from './report_failure';
|
||||||
|
|
||||||
|
jest.mock('./github_api');
|
||||||
|
const { GithubApi } = jest.requireMock('./github_api');
|
||||||
|
|
||||||
|
describe('createFailureIssue()', () => {
|
||||||
|
it('creates new github issue with failure text, link to issue, and valid metadata', async () => {
|
||||||
|
const log = new ToolingLog();
|
||||||
|
const writer = new ToolingLogCollectingWriter();
|
||||||
|
log.setWriters([writer]);
|
||||||
|
|
||||||
|
const api = new GithubApi();
|
||||||
|
|
||||||
|
await createFailureIssue(
|
||||||
|
'https://build-url',
|
||||||
|
{
|
||||||
|
classname: 'some.classname',
|
||||||
|
failure: 'this is the failure text',
|
||||||
|
name: 'test name',
|
||||||
|
time: '2018-01-01T01:00:00Z',
|
||||||
|
},
|
||||||
|
log,
|
||||||
|
api
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(api.createIssue).toMatchInlineSnapshot(`
|
||||||
|
[MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [
|
||||||
|
"Failing test: some.classname - test name",
|
||||||
|
"A test failed on a tracked branch
|
||||||
|
\`\`\`
|
||||||
|
this is the failure text
|
||||||
|
\`\`\`
|
||||||
|
First failure: [Jenkins Build](https://build-url)
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {\\"failed-test\\":{\\"test.class\\":\\"some.classname\\",\\"test.name\\":\\"test name\\",\\"test.failCount\\":1}} -->",
|
||||||
|
Array [
|
||||||
|
"failed-test",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(writer.messages).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
" [34minfo[39m Created issue undefined",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updatedFailureIssue()', () => {
|
||||||
|
it('increments failure count and adds new comment to issue', async () => {
|
||||||
|
const log = new ToolingLog();
|
||||||
|
const writer = new ToolingLogCollectingWriter();
|
||||||
|
log.setWriters([writer]);
|
||||||
|
|
||||||
|
const api = new GithubApi();
|
||||||
|
|
||||||
|
await updatedFailureIssue(
|
||||||
|
'https://build-url',
|
||||||
|
{
|
||||||
|
html_url: 'https://github.com/issues/1234',
|
||||||
|
labels: ['some-label'],
|
||||||
|
number: 1234,
|
||||||
|
title: 'issue title',
|
||||||
|
body: dedent`
|
||||||
|
# existing issue body
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {"failed-test":{"test.failCount":10}} -->"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
log,
|
||||||
|
api
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(api.editIssueBodyAndEnsureOpen).toMatchInlineSnapshot(`
|
||||||
|
[MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [
|
||||||
|
1234,
|
||||||
|
"# existing issue body
|
||||||
|
|
||||||
|
<!-- kibanaCiData = {\\"failed-test\\":{\\"test.failCount\\":11}} -->\\"",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(api.addIssueComment).toMatchInlineSnapshot(`
|
||||||
|
[MockFunction] {
|
||||||
|
"calls": Array [
|
||||||
|
Array [
|
||||||
|
1234,
|
||||||
|
"New failure: [Jenkins Build](https://build-url)",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"results": Array [
|
||||||
|
Object {
|
||||||
|
"type": "return",
|
||||||
|
"value": undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(writer.messages).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
" [34minfo[39m Updated issue https://github.com/issues/1234, failCount: 11",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ToolingLog } from '@kbn/dev-utils';
|
||||||
|
import dedent from 'dedent';
|
||||||
|
|
||||||
|
import { TestFailure } from './get_failures';
|
||||||
|
import { GithubIssue, GithubApi } from './github_api';
|
||||||
|
import { getIssueMetadata, updateIssueMetadata } from './issue_metadata';
|
||||||
|
|
||||||
|
export async function createFailureIssue(
|
||||||
|
buildUrl: string,
|
||||||
|
failure: TestFailure,
|
||||||
|
log: ToolingLog,
|
||||||
|
api: GithubApi
|
||||||
|
) {
|
||||||
|
const title = `Failing test: ${failure.classname} - ${failure.name}`;
|
||||||
|
|
||||||
|
const body = updateIssueMetadata(
|
||||||
|
dedent`
|
||||||
|
A test failed on a tracked branch
|
||||||
|
\`\`\`
|
||||||
|
${failure.failure}
|
||||||
|
\`\`\`
|
||||||
|
First failure: [Jenkins Build](${buildUrl})
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
'test.class': failure.classname,
|
||||||
|
'test.name': failure.name,
|
||||||
|
'test.failCount': 1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const newIssueUrl = await api.createIssue(title, body, ['failed-test']);
|
||||||
|
log.info(`Created issue ${newIssueUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updatedFailureIssue(
|
||||||
|
buildUrl: string,
|
||||||
|
issue: GithubIssue,
|
||||||
|
log: ToolingLog,
|
||||||
|
api: GithubApi
|
||||||
|
) {
|
||||||
|
// Increment failCount
|
||||||
|
const newCount = getIssueMetadata(issue.body, 'test.failCount', 0) + 1;
|
||||||
|
const newBody = updateIssueMetadata(issue.body, {
|
||||||
|
'test.failCount': newCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
await api.editIssueBodyAndEnsureOpen(issue.number, newBody);
|
||||||
|
await api.addIssueComment(issue.number, `New failure: [Jenkins Build](${buildUrl})`);
|
||||||
|
|
||||||
|
log.info(`Updated issue ${issue.html_url}, failCount: ${newCount}`);
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { REPO_ROOT, run, createFailError, createFlagError } from '@kbn/dev-utils';
|
||||||
|
import globby from 'globby';
|
||||||
|
|
||||||
|
import { getFailures } from './get_failures';
|
||||||
|
import { GithubApi } from './github_api';
|
||||||
|
import { updatedFailureIssue, createFailureIssue } from './report_failure';
|
||||||
|
import { getIssueMetadata } from './issue_metadata';
|
||||||
|
|
||||||
|
export function runFailedTestsReporterCli() {
|
||||||
|
run(
|
||||||
|
async ({ log, flags }) => {
|
||||||
|
const buildUrl = flags['build-url'];
|
||||||
|
if (typeof buildUrl !== 'string' || !buildUrl) {
|
||||||
|
throw createFlagError('Missing --build-url or process.env.BUILD_URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
const dryRun = !!flags['dry-run'];
|
||||||
|
if (!dryRun) {
|
||||||
|
// JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others
|
||||||
|
const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//);
|
||||||
|
const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH;
|
||||||
|
if (!branch) {
|
||||||
|
throw createFailError(
|
||||||
|
'Unable to determine originating branch from job name or other environment variables'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPr = !!process.env.ghprbPullId;
|
||||||
|
const isMasterOrVersion =
|
||||||
|
branch.match(/^(origin\/){0,1}master$/) || branch.match(/^(origin\/){0,1}\d+\.(x|\d+)$/);
|
||||||
|
if (!isMasterOrVersion || isPr) {
|
||||||
|
throw createFailError('Failure issues only created on master/version branch jobs', {
|
||||||
|
exitCode: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.GITHUB_TOKEN) {
|
||||||
|
throw createFailError(
|
||||||
|
'GITHUB_TOKEN environment variable must be set, otherwise use --dry-run flag'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const githubApi = new GithubApi(log, process.env.GITHUB_TOKEN, dryRun);
|
||||||
|
const issues = await githubApi.getAllFailedTestIssues();
|
||||||
|
const reportPaths = await globby(['target/junit/**/*.xml'], {
|
||||||
|
cwd: REPO_ROOT,
|
||||||
|
absolute: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const reportPath of reportPaths) {
|
||||||
|
for (const failure of await getFailures(log, reportPath)) {
|
||||||
|
const existingIssue = issues.find(
|
||||||
|
i =>
|
||||||
|
getIssueMetadata(i.body, 'test.class') === failure.classname &&
|
||||||
|
getIssueMetadata(i.body, 'test.name') === failure.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingIssue) {
|
||||||
|
await updatedFailureIssue(buildUrl, existingIssue, log, githubApi);
|
||||||
|
} else {
|
||||||
|
await createFailureIssue(buildUrl, failure, log, githubApi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: `a cli that opens issues or updates existing issues based on junit reports`,
|
||||||
|
flags: {
|
||||||
|
boolean: ['dry-run'],
|
||||||
|
string: ['build-url'],
|
||||||
|
default: {
|
||||||
|
'build-url': process.env.BUILD_URL,
|
||||||
|
},
|
||||||
|
help: `
|
||||||
|
--dry-run Execute the CLI without contacting Github
|
||||||
|
--build-url URL of the failed build, defaults to process.env.BUILD_URL
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -40,3 +40,5 @@ export { readConfigFile } from './functional_test_runner/lib/config/read_config_
|
||||||
|
|
||||||
// @ts-ignore not typed yet
|
// @ts-ignore not typed yet
|
||||||
export { runFtrCli } from './functional_test_runner/cli';
|
export { runFtrCli } from './functional_test_runner/cli';
|
||||||
|
|
||||||
|
export { runFailedTestsReporterCli } from './failed_tests_reporter';
|
||||||
|
|
21
scripts/report_failed_tests.js
Normal file
21
scripts/report_failed_tests.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('../src/setup_node_env');
|
||||||
|
require('@kbn/test').runFailedTestsReporterCli();
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch B.V. licenses this file to you 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
|
|
||||||
*
|
|
||||||
* http://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 { resolve } = require('path');
|
|
||||||
|
|
||||||
// force cwd
|
|
||||||
process.chdir(resolve(__dirname, '../../..'));
|
|
||||||
|
|
||||||
// JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others
|
|
||||||
const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//);
|
|
||||||
const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH;
|
|
||||||
if (!branch) {
|
|
||||||
console.log('Unable to determine originating branch from job name or other environment variables');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPr = !!process.env.ghprbPullId;
|
|
||||||
const isMasterOrVersion = branch.match(/^(origin\/){0,1}master$/) || branch.match(/^(origin\/){0,1}\d+\.(x|\d+)$/);
|
|
||||||
if (!isMasterOrVersion || isPr) {
|
|
||||||
console.log('Failure issues only created on master/version branch jobs');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
require('../../setup_node_env');
|
|
||||||
require('./report').reportFailedTests();
|
|
|
@ -1,196 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch B.V. licenses this file to you 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
|
|
||||||
*
|
|
||||||
* http://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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import xml2js from 'xml2js';
|
|
||||||
import vfs from 'vinyl-fs';
|
|
||||||
import { createMapStream } from '../../legacy/utils/streams';
|
|
||||||
import { getGithubClient, markdownMetadata, paginate } from '../github_utils';
|
|
||||||
import { find } from 'lodash';
|
|
||||||
import stripAnsi from 'strip-ansi';
|
|
||||||
|
|
||||||
const GITHUB_FLAKY_TEST_LABEL = 'failed-test';
|
|
||||||
const GITHUB_OWNER = 'elastic';
|
|
||||||
const GITHUB_REPO = 'kibana';
|
|
||||||
const BUILD_URL = process.env.BUILD_URL;
|
|
||||||
|
|
||||||
const indent = text => (
|
|
||||||
` ${text.split('\n').map(l => ` ${l}`).join('\n')}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const getFailureText = (testCase) => {
|
|
||||||
const [failureNode] = testCase.failure;
|
|
||||||
|
|
||||||
if (failureNode && typeof failureNode === 'object' && typeof failureNode._ === 'string') {
|
|
||||||
return stripAnsi(failureNode._);
|
|
||||||
}
|
|
||||||
|
|
||||||
return stripAnsi(String(failureNode));
|
|
||||||
};
|
|
||||||
|
|
||||||
const isLikelyIrrelevant = ({ name, failure }) => {
|
|
||||||
if (failure.includes('NoSuchSessionError: This driver instance does not have a valid session ID')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failure.includes('Error: No Living connections')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.includes('"after all" hook') && failure.includes(`Cannot read property 'shutdown' of undefined`)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failure.includes('Unable to read artifact info') && failure.includes('Service Temporarily Unavailable')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failure.includes('Unable to fetch Kibana status API response from Kibana')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses junit XML files into JSON
|
|
||||||
*/
|
|
||||||
export const mapXml = () => createMapStream((file) => new Promise((resolve, reject) => {
|
|
||||||
xml2js.parseString(file.contents.toString(), (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
resolve(result);
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters all testsuites to find failed testcases
|
|
||||||
*/
|
|
||||||
export const filterFailures = () => createMapStream((testSuite) => {
|
|
||||||
// Grab the failures. Reporters may report multiple testsuites in a single file.
|
|
||||||
const testFiles = testSuite.testsuites
|
|
||||||
? testSuite.testsuites.testsuite
|
|
||||||
: [testSuite.testsuite];
|
|
||||||
|
|
||||||
const failures = [];
|
|
||||||
for (const testFile of testFiles) {
|
|
||||||
for (const testCase of testFile.testcase) {
|
|
||||||
if (!testCase.failure) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// unwrap xml weirdness
|
|
||||||
const failureCase = {
|
|
||||||
...testCase.$,
|
|
||||||
// Strip ANSI color characters
|
|
||||||
failure: getFailureText(testCase)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLikelyIrrelevant(failureCase)) {
|
|
||||||
console.log(`Ignoring likely irrelevant failure: ${failureCase.classname} - ${failureCase.name}\n${indent(failureCase.failure)}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
failures.push(failureCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Found ${failures.length} test failures`);
|
|
||||||
|
|
||||||
return failures;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and updates github issues for the given testcase failures.
|
|
||||||
*/
|
|
||||||
const updateGithubIssues = (githubClient, issues) => {
|
|
||||||
return createMapStream(async (failureCases) => {
|
|
||||||
|
|
||||||
await Promise.all(failureCases.map(async (failureCase) => {
|
|
||||||
const existingIssue = find(issues, (issue) => {
|
|
||||||
return markdownMetadata.get(issue.body, 'test.class') === failureCase.classname &&
|
|
||||||
markdownMetadata.get(issue.body, 'test.name') === failureCase.name;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingIssue) {
|
|
||||||
// Increment failCount
|
|
||||||
const newCount = (markdownMetadata.get(existingIssue.body, 'test.failCount') || 0) + 1;
|
|
||||||
const newBody = markdownMetadata.set(existingIssue.body, 'test.failCount', newCount);
|
|
||||||
|
|
||||||
await githubClient.issues.edit({
|
|
||||||
owner: GITHUB_OWNER,
|
|
||||||
repo: GITHUB_REPO,
|
|
||||||
number: existingIssue.number,
|
|
||||||
state: 'open', // Reopen issue if it was closed.
|
|
||||||
body: newBody
|
|
||||||
});
|
|
||||||
|
|
||||||
// Append a new comment
|
|
||||||
await githubClient.issues.createComment({
|
|
||||||
owner: GITHUB_OWNER,
|
|
||||||
repo: GITHUB_REPO,
|
|
||||||
number: existingIssue.number,
|
|
||||||
body: `New failure: [Jenkins Build](${BUILD_URL})`
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Updated issue ${existingIssue.html_url}, failCount: ${newCount}`);
|
|
||||||
} else {
|
|
||||||
let body = 'A test failed on a tracked branch\n' +
|
|
||||||
'```\n' + failureCase.failure + '\n```\n' +
|
|
||||||
`First failure: [Jenkins Build](${BUILD_URL})`;
|
|
||||||
body = markdownMetadata.set(body, {
|
|
||||||
'test.class': failureCase.classname,
|
|
||||||
'test.name': failureCase.name,
|
|
||||||
'test.failCount': 1
|
|
||||||
});
|
|
||||||
|
|
||||||
const newIssue = await githubClient.issues.create({
|
|
||||||
owner: GITHUB_OWNER,
|
|
||||||
repo: GITHUB_REPO,
|
|
||||||
title: `Failing test: ${failureCase.classname} - ${failureCase.name}`,
|
|
||||||
body: body,
|
|
||||||
labels: [GITHUB_FLAKY_TEST_LABEL]
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Created issue ${newIssue.data.html_url}`);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return failureCases;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scans all junit XML files in ./target/junit/ and reports any found test failures to Github Issues.
|
|
||||||
*/
|
|
||||||
export async function reportFailedTests() {
|
|
||||||
const githubClient = getGithubClient();
|
|
||||||
const issues = await paginate(githubClient, githubClient.issues.getForRepo({
|
|
||||||
owner: GITHUB_OWNER,
|
|
||||||
repo: GITHUB_REPO,
|
|
||||||
labels: GITHUB_FLAKY_TEST_LABEL,
|
|
||||||
state: 'all',
|
|
||||||
per_page: 100
|
|
||||||
}));
|
|
||||||
|
|
||||||
vfs
|
|
||||||
.src(['./target/junit/**/*.xml'])
|
|
||||||
.pipe(mapXml())
|
|
||||||
.pipe(filterFailures())
|
|
||||||
.pipe(updateGithubIssues(githubClient, issues))
|
|
||||||
.on('done', () => console.log(`Finished reporting test failures.`));
|
|
||||||
}
|
|
|
@ -1,193 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch B.V. licenses this file to you 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
|
|
||||||
*
|
|
||||||
* http://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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* eslint-disable max-len */
|
|
||||||
|
|
||||||
import { resolve } from 'path';
|
|
||||||
|
|
||||||
import vfs from 'vinyl-fs';
|
|
||||||
|
|
||||||
import { mapXml, filterFailures } from './report';
|
|
||||||
import { createPromiseFromStreams } from '../../legacy/utils/streams/promise_from_streams';
|
|
||||||
import { createConcatStream } from '../../legacy/utils/streams/concat_stream';
|
|
||||||
|
|
||||||
console.log = jest.fn();
|
|
||||||
afterEach(() => jest.resetAllMocks());
|
|
||||||
|
|
||||||
describe('irrelevant failure filtering', () => {
|
|
||||||
describe('jest report', () => {
|
|
||||||
it('allows relevant tests', async () => {
|
|
||||||
const failures = await createPromiseFromStreams([
|
|
||||||
vfs.src([resolve(__dirname, '__fixtures__/jest_report.xml')]),
|
|
||||||
mapXml(),
|
|
||||||
filterFailures(),
|
|
||||||
createConcatStream(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(console.log.mock.calls).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
"Found 1 test failures",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
expect(failures).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"classname": "X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp",
|
|
||||||
"failure": "
|
|
||||||
TypeError: Cannot read property '0' of undefined
|
|
||||||
at Object.<anonymous>.test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10)
|
|
||||||
",
|
|
||||||
"name": "launcher can reconnect if process died",
|
|
||||||
"time": "7.060",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ftr report', () => {
|
|
||||||
it('allows relevant tests', async () => {
|
|
||||||
const failures = await createPromiseFromStreams([
|
|
||||||
vfs.src([resolve(__dirname, '__fixtures__/ftr_report.xml')]),
|
|
||||||
mapXml(),
|
|
||||||
filterFailures(),
|
|
||||||
createConcatStream(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(console.log.mock.calls).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
"Ignoring likely irrelevant failure: Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps - maps app \\"after all\\" hook
|
|
||||||
|
|
||||||
{ NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used.
|
|
||||||
at promise.finally (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:726:38)
|
|
||||||
at Object.thenFinally [as finally] (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/promise.js:124:12)
|
|
||||||
at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }
|
|
||||||
",
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
"Found 1 test failures",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
expect(failures).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"classname": "Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js",
|
|
||||||
"failure": "
|
|
||||||
Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj~=\\"layerTocActionsPanelToggleButtonRoad_Map_-_Bright\\"])
|
|
||||||
Wait timed out after 10055ms
|
|
||||||
at /var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:834:17
|
|
||||||
at process._tickCallback (internal/process/next_tick.js:68:7)
|
|
||||||
at lastError (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:28:9)
|
|
||||||
at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13)
|
|
||||||
",
|
|
||||||
"name": "maps app maps loaded from sample data ecommerce \\"before all\\" hook",
|
|
||||||
"time": "154.378",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('mocha report', () => {
|
|
||||||
it('allows relevant tests', async () => {
|
|
||||||
const failures = await createPromiseFromStreams([
|
|
||||||
vfs.src([resolve(__dirname, '__fixtures__/mocha_report.xml')]),
|
|
||||||
mapXml(),
|
|
||||||
filterFailures(),
|
|
||||||
createConcatStream(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(console.log.mock.calls).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
"Ignoring likely irrelevant failure: X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts - code in multiple nodes \\"before all\\" hook
|
|
||||||
|
|
||||||
Error: Unable to read artifact info from https://artifacts-api.elastic.co/v1/versions/8.0.0-SNAPSHOT/builds/latest/projects/elasticsearch: Service Temporarily Unavailable
|
|
||||||
<html>
|
|
||||||
<head><title>503 Service Temporarily Unavailable</title></head>
|
|
||||||
<body bgcolor=\\"white\\">
|
|
||||||
<center><h1>503 Service Temporarily Unavailable</h1></center>
|
|
||||||
<hr><center>nginx/1.13.7</center>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
at Function.getSnapshot (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/packages/kbn-es/src/artifact.js:95:13)
|
|
||||||
at process._tickCallback (internal/process/next_tick.js:68:7)
|
|
||||||
",
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
"Ignoring likely irrelevant failure: X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts - code in multiple nodes \\"after all\\" hook
|
|
||||||
|
|
||||||
TypeError: Cannot read property 'shutdown' of undefined
|
|
||||||
at Context.shutdown (plugins/code/server/__tests__/multi_node.ts:125:23)
|
|
||||||
at process.topLevelDomainCallback (domain.js:120:23)
|
|
||||||
",
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
"Found 0 test failures",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
expect(failures).toMatchInlineSnapshot(`Array []`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('karma report', () => {
|
|
||||||
it('allows relevant tests', async () => {
|
|
||||||
const failures = await createPromiseFromStreams([
|
|
||||||
vfs.src([resolve(__dirname, '__fixtures__/karma_report.xml')]),
|
|
||||||
mapXml(),
|
|
||||||
filterFailures(),
|
|
||||||
createConcatStream(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(console.log.mock.calls).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
"Found 1 test failures",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
expect(failures).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"classname": "Browser Unit Tests.CoordinateMapsVisualizationTest",
|
|
||||||
"failure": "Error: expected 7069 to be below 64
|
|
||||||
at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11)
|
|
||||||
at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8)
|
|
||||||
at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15)
|
|
||||||
at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60)
|
|
||||||
at tryCatch (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:62:40)
|
|
||||||
at Generator.invoke [as _invoke] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:288:22)
|
|
||||||
at Generator.prototype.<computed> [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21)
|
|
||||||
at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103)
|
|
||||||
at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194)
|
|
||||||
",
|
|
||||||
"name": "CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK",
|
|
||||||
"time": "0.265",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -109,13 +109,7 @@ export default class JestJUnitReporter {
|
||||||
`TEST-${process.env.JOB ? process.env.JOB + '-' : ''}${reportName}.xml`
|
`TEST-${process.env.JOB ? process.env.JOB + '-' : ''}${reportName}.xml`
|
||||||
);
|
);
|
||||||
|
|
||||||
const reportXML = root.end({
|
const reportXML = root.end();
|
||||||
pretty: true,
|
|
||||||
indent: ' ',
|
|
||||||
newline: '\n',
|
|
||||||
spacebeforeslash: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
mkdirSync(dirname(reportPath), { recursive: true });
|
mkdirSync(dirname(reportPath), { recursive: true });
|
||||||
writeFileSync(reportPath, reportXML, 'utf8');
|
writeFileSync(reportPath, reportXML, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,13 +142,7 @@ export function setupJUnitReportGeneration(runner, options = {}) {
|
||||||
`TEST-${process.env.JOB ? process.env.JOB + '-' : ''}${reportName}.xml`
|
`TEST-${process.env.JOB ? process.env.JOB + '-' : ''}${reportName}.xml`
|
||||||
);
|
);
|
||||||
|
|
||||||
const reportXML = builder.end({
|
const reportXML = builder.end();
|
||||||
pretty: true,
|
|
||||||
indent: ' ',
|
|
||||||
newline: '\n',
|
|
||||||
spacebeforeslash: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
mkdirSync(dirname(reportPath), { recursive: true });
|
mkdirSync(dirname(reportPath), { recursive: true });
|
||||||
writeFileSync(reportPath, reportXML, 'utf8');
|
writeFileSync(reportPath, reportXML, 'utf8');
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
||||||
trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT
|
trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT
|
||||||
else
|
else
|
||||||
source src/dev/ci_setup/setup_env.sh
|
source src/dev/ci_setup/setup_env.sh
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
||||||
trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT
|
trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT
|
||||||
else
|
else
|
||||||
source src/dev/ci_setup/setup_env.sh
|
source src/dev/ci_setup/setup_env.sh
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
||||||
trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT
|
trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export TEST_BROWSER_HEADLESS=1
|
export TEST_BROWSER_HEADLESS=1
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
||||||
trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT
|
trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT
|
||||||
else
|
else
|
||||||
source src/dev/ci_setup/setup_env.sh
|
source src/dev/ci_setup/setup_env.sh
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
||||||
trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT
|
trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export TEST_BROWSER_HEADLESS=1
|
export TEST_BROWSER_HEADLESS=1
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
||||||
trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT
|
trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT
|
||||||
else
|
else
|
||||||
source src/dev/ci_setup/setup_env.sh
|
source src/dev/ci_setup/setup_env.sh
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
||||||
trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT
|
trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT
|
||||||
else
|
else
|
||||||
source src/dev/ci_setup/setup_env.sh
|
source src/dev/ci_setup/setup_env.sh
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
if [[ -z "$IS_PIPELINE_JOB" ]] ; then
|
||||||
trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT
|
trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT
|
||||||
else
|
else
|
||||||
source src/dev/ci_setup/setup_env.sh
|
source src/dev/ci_setup/setup_env.sh
|
||||||
fi
|
fi
|
||||||
|
|
65
yarn.lock
65
yarn.lock
|
@ -1485,14 +1485,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.3.tgz#dfa0c92efe44a1d1a7974fb49ffeb40ef2da5a27"
|
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.3.tgz#dfa0c92efe44a1d1a7974fb49ffeb40ef2da5a27"
|
||||||
integrity sha512-zVgvPwGK7c1aVdUVc9Qv7SqepOGRDrqCw7KZPSZziWGxSlbII3gmvGLPzLX4d0n0BMbamBacUrN22zOMyFFEkQ==
|
integrity sha512-zVgvPwGK7c1aVdUVc9Qv7SqepOGRDrqCw7KZPSZziWGxSlbII3gmvGLPzLX4d0n0BMbamBacUrN22zOMyFFEkQ==
|
||||||
|
|
||||||
"@gimenete/type-writer@^0.1.3":
|
|
||||||
version "0.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@gimenete/type-writer/-/type-writer-0.1.3.tgz#2d4f26118b18d71f5b34ca24fdd6d1fd455c05b6"
|
|
||||||
integrity sha512-vhpvVfM/fYqb1aAnkgOvtDKoOgU3ZYIvDnKSDAFSoBvallmGURMlHOE0/VG/gqunUZVXGCFBGHxI8swjBh+sIA==
|
|
||||||
dependencies:
|
|
||||||
camelcase "^5.0.0"
|
|
||||||
prettier "^1.13.7"
|
|
||||||
|
|
||||||
"@gulp-sourcemaps/identity-map@1.X":
|
"@gulp-sourcemaps/identity-map@1.X":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz#1e6fe5d8027b1f285dc0d31762f566bccd73d5a9"
|
resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz#1e6fe5d8027b1f285dc0d31762f566bccd73d5a9"
|
||||||
|
@ -2323,21 +2315,6 @@
|
||||||
once "^1.4.0"
|
once "^1.4.0"
|
||||||
universal-user-agent "^2.0.1"
|
universal-user-agent "^2.0.1"
|
||||||
|
|
||||||
"@octokit/rest@^15.10.0":
|
|
||||||
version "15.10.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.10.0.tgz#9baf7430e55edf1a1024c35ae72ed2f5fc6e90e9"
|
|
||||||
integrity sha512-xZ4ejCZoqvKrIN3tQOKZlJ6nDQxaOdLcjRsamDnbckU7V5YTn2xheIqFXnQ2vLvxqVwyI8+2dfsODYbHxtwtSw==
|
|
||||||
dependencies:
|
|
||||||
"@gimenete/type-writer" "^0.1.3"
|
|
||||||
before-after-hook "^1.1.0"
|
|
||||||
btoa-lite "^1.0.0"
|
|
||||||
debug "^3.1.0"
|
|
||||||
http-proxy-agent "^2.1.0"
|
|
||||||
https-proxy-agent "^2.2.0"
|
|
||||||
lodash "^4.17.4"
|
|
||||||
node-fetch "^2.1.1"
|
|
||||||
url-template "^2.0.8"
|
|
||||||
|
|
||||||
"@octokit/rest@^16.23.2":
|
"@octokit/rest@^16.23.2":
|
||||||
version "16.23.2"
|
version "16.23.2"
|
||||||
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.23.2.tgz#975e84610427c4ab6c41bec77c24aed9b7563db4"
|
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.23.2.tgz#975e84610427c4ab6c41bec77c24aed9b7563db4"
|
||||||
|
@ -3798,6 +3775,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/parse-link-header@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/parse-link-header/-/parse-link-header-1.0.0.tgz#69f059e40a0fa93dc2e095d4142395ae6adc5d7a"
|
||||||
|
integrity sha512-fCA3btjE7QFeRLfcD0Sjg+6/CnmC66HpMBoRfRzd2raTaWMJV21CCZ0LO8MOqf8onl5n0EPfjq4zDhbyX8SVwA==
|
||||||
|
|
||||||
"@types/pngjs@^3.3.2":
|
"@types/pngjs@^3.3.2":
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4"
|
resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4"
|
||||||
|
@ -4072,6 +4054,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/strip-ansi/-/strip-ansi-3.0.0.tgz#9b63d453a6b54aa849182207711a08be8eea48ae"
|
resolved "https://registry.yarnpkg.com/@types/strip-ansi/-/strip-ansi-3.0.0.tgz#9b63d453a6b54aa849182207711a08be8eea48ae"
|
||||||
integrity sha1-m2PUU6a1SqhJGCIHcRoIvo7qSK4=
|
integrity sha1-m2PUU6a1SqhJGCIHcRoIvo7qSK4=
|
||||||
|
|
||||||
|
"@types/strip-ansi@^5.2.1":
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/strip-ansi/-/strip-ansi-5.2.1.tgz#acd97f1f091e332bb7ce697c4609eb2370fa2a92"
|
||||||
|
integrity sha512-1l5iM0LBkVU8JXxnIoBqNvg+yyOXxPeN6DNoD+7A9AN1B8FhYPSeIXgyNqwIqg1uzipTgVC2hmuDzB0u9qw/PA==
|
||||||
|
dependencies:
|
||||||
|
strip-ansi "*"
|
||||||
|
|
||||||
"@types/strong-log-transformer@^1.0.0":
|
"@types/strong-log-transformer@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/strong-log-transformer/-/strong-log-transformer-1.0.0.tgz#47b0c9fe1f0c997ed4239746e633e8e36fc836ac"
|
resolved "https://registry.yarnpkg.com/@types/strong-log-transformer/-/strong-log-transformer-1.0.0.tgz#47b0c9fe1f0c997ed4239746e633e8e36fc836ac"
|
||||||
|
@ -4219,6 +4208,13 @@
|
||||||
"@types/events" "*"
|
"@types/events" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/xml2js@^0.4.5":
|
||||||
|
version "0.4.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.5.tgz#d21759b056f282d9c7066f15bbf5c19b908f22fa"
|
||||||
|
integrity sha512-yohU3zMn0fkhlape1nxXG2bLEGZRc1FeqF80RoHaYXJN7uibaauXfhzhOJr1Xh36sn+/tx21QAOf07b/xYVk1w==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "13.0.0"
|
version "13.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0"
|
||||||
|
@ -6525,11 +6521,6 @@ bcrypt-pbkdf@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tweetnacl "^0.14.3"
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
before-after-hook@^1.1.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.1.0.tgz#83165e15a59460d13702cb8febd6a1807896db5a"
|
|
||||||
integrity sha512-VOMDtYPwLbIncTxNoSzRyvaMxtXmLWLUqr8k5AfC1BzLk34HvBXaQX8snOwQZ4c0aX8aSERqtJSiI9/m2u5kuA==
|
|
||||||
|
|
||||||
before-after-hook@^1.4.0:
|
before-after-hook@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.4.0.tgz#2b6bf23dca4f32e628fd2747c10a37c74a4b484d"
|
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.4.0.tgz#2b6bf23dca4f32e628fd2747c10a37c74a4b484d"
|
||||||
|
@ -14931,7 +14922,7 @@ https-browserify@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||||
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
||||||
|
|
||||||
https-proxy-agent@2.2.1, https-proxy-agent@^2.2.0, https-proxy-agent@^2.2.1:
|
https-proxy-agent@2.2.1, https-proxy-agent@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||||
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
|
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
|
||||||
|
@ -19865,11 +19856,6 @@ node-fetch@2.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
|
||||||
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
|
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
|
||||||
|
|
||||||
node-fetch@^2.1.1:
|
|
||||||
version "2.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.1.tgz#1fe551e0ded6c45b3b3b937d0fb46f76df718d1e"
|
|
||||||
integrity sha512-ObXBpNCD3A/vYQiQtEWl7DuqjAXjfptYFuGHLdPl5U19/6kJuZV+8uMHLrkj3wJrJoyfg4nhgyFixZdaZoAiEQ==
|
|
||||||
|
|
||||||
node-fetch@^2.3.0:
|
node-fetch@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5"
|
||||||
|
@ -21199,6 +21185,13 @@ parse-json@^5.0.0:
|
||||||
json-parse-better-errors "^1.0.1"
|
json-parse-better-errors "^1.0.1"
|
||||||
lines-and-columns "^1.1.6"
|
lines-and-columns "^1.1.6"
|
||||||
|
|
||||||
|
parse-link-header@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse-link-header/-/parse-link-header-1.0.1.tgz#bedfe0d2118aeb84be75e7b025419ec8a61140a7"
|
||||||
|
integrity sha1-vt/g0hGK64S+deewJUGeyKYRQKc=
|
||||||
|
dependencies:
|
||||||
|
xtend "~4.0.1"
|
||||||
|
|
||||||
parse-ms@^2.0.0:
|
parse-ms@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d"
|
resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d"
|
||||||
|
@ -21887,7 +21880,7 @@ prettier@1.16.4:
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
|
||||||
integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
|
integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
|
||||||
|
|
||||||
prettier@1.18.2, prettier@^1.13.7, prettier@^1.18.2:
|
prettier@1.18.2, prettier@^1.18.2:
|
||||||
version "1.18.2"
|
version "1.18.2"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
|
||||||
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
|
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
|
||||||
|
@ -26234,7 +26227,7 @@ stringstream@~0.0.4, stringstream@~0.0.5:
|
||||||
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
|
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
|
||||||
integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==
|
integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==
|
||||||
|
|
||||||
strip-ansi@5.2.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
strip-ansi@*, strip-ansi@5.2.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
||||||
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
|
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
|
||||||
|
|
Loading…
Reference in a new issue