зеркало из https://github.com/mozilla/treeherder.git
Bug 1615330 - Support test filtering besides manifest filtering (#6172)
This change allows the user to enter a test/manifest path and find jobs that match it. This supports the code landed in [bug 1615333](https://bugzilla.mozilla.org/show_bug.cgi?id=1615333) We only fetch test manifest artifacts when `test_paths` is part of the URL. Also added a test to correctly map tests/manifest from the task name, thus, increase of code coverage. The code was originally landed here:0f9e053096
and reverted here:3224c217f5
This commit is contained in:
Родитель
ce35ba1b5d
Коммит
2fdc52a474
|
@ -0,0 +1,12 @@
|
|||
import { gzip } from 'pako';
|
||||
|
||||
import decompress from '../../../ui/helpers/gzip';
|
||||
|
||||
describe('gzip related functions', () => {
|
||||
test('compress and decompress', async () => {
|
||||
const str = JSON.stringify({ foo: 'bar' });
|
||||
const compressed = await gzip(str);
|
||||
const decompressed = await decompress(compressed);
|
||||
expect(JSON.stringify(decompressed)).toBe(str);
|
||||
});
|
||||
});
|
|
@ -2,16 +2,42 @@ import React from 'react';
|
|||
import fetchMock from 'fetch-mock';
|
||||
import { Provider } from 'react-redux';
|
||||
import { render, cleanup, waitForElement } from '@testing-library/react';
|
||||
import { gzip } from 'pako';
|
||||
|
||||
import { getProjectUrl, replaceLocation } from '../../../ui/helpers/location';
|
||||
import FilterModel from '../../../ui/models/filter';
|
||||
import pushListFixture from '../mock/push_list';
|
||||
import jobListFixture from '../mock/job_list/job_2';
|
||||
import configureStore from '../../../ui/job-view/redux/configureStore';
|
||||
import Push from '../../../ui/job-view/pushes/Push';
|
||||
import Push, { joinArtifacts } from '../../../ui/job-view/pushes/Push';
|
||||
import { getApiUrl } from '../../../ui/helpers/url';
|
||||
import { findInstance } from '../../../ui/helpers/job';
|
||||
|
||||
const testsByManifest = {
|
||||
'devtools/client/framework/browser-toolbox/test/browser.ini': [
|
||||
'browser_browser_toolbox.js',
|
||||
'browser_browser_toolbox_debugger.js',
|
||||
'browser_browser_toolbox_fission_contentframe_inspector.js',
|
||||
'browser_browser_toolbox_fission_inspector.js',
|
||||
'browser_browser_toolbox_rtl.js',
|
||||
],
|
||||
'devtools/client/framework/test/browser.ini': ['foo.js'],
|
||||
};
|
||||
const manifestsByTask = {
|
||||
'test-linux1804-64/debug-mochitest-devtools-chrome-e10s-1': [
|
||||
'devtools/client/framework/browser-toolbox/test/browser.ini',
|
||||
'devtools/client/framework/test/browser.ini',
|
||||
'devtools/client/framework/test/metrics/browser_metrics_inspector.ini',
|
||||
'devtools/client/inspector/changes/test/browser.ini',
|
||||
'devtools/client/inspector/extensions/test/browser.ini',
|
||||
'devtools/client/inspector/markup/test/browser.ini',
|
||||
'devtools/client/jsonview/test/browser.ini',
|
||||
'devtools/client/shared/test/browser.ini',
|
||||
'devtools/client/styleeditor/test/browser.ini',
|
||||
'devtools/client/webconsole/test/node/fixtures/stubs/stubs.ini',
|
||||
],
|
||||
};
|
||||
|
||||
describe('Push', () => {
|
||||
const repoName = 'autoland';
|
||||
const currentRepo = {
|
||||
|
@ -40,7 +66,7 @@ describe('Push', () => {
|
|||
</Provider>
|
||||
);
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
fetchMock.get(getProjectUrl('/push/?full=true&count=10', repoName), {
|
||||
...pushListFixture,
|
||||
results: pushListFixture.results[1],
|
||||
|
@ -49,21 +75,22 @@ describe('Push', () => {
|
|||
getApiUrl('/jobs/?push_id=511137', repoName),
|
||||
jobListFixture,
|
||||
);
|
||||
fetchMock.get(
|
||||
'https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/gecko.v2.autoland.revision.d5b037941b0ebabcc9b843f24d926e9d65961087.taskgraph.decision/artifacts/public/manifests-by-task.json.gz',
|
||||
404,
|
||||
);
|
||||
fetchMock.get(
|
||||
'https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/gecko.v2.autoland.revision.d5b037941b0ebabcc9b843f24d926e9d65961087.taskgraph.decision/artifacts/public/manifests-by-task.json',
|
||||
{
|
||||
'test-linux1804-64/debug-mochitest-devtools-chrome-e10s-5': [
|
||||
'devtools/client/inspector/compatibility/test/browser/browser.ini',
|
||||
'devtools/client/inspector/grids/test/browser.ini',
|
||||
'devtools/client/inspector/rules/test/browser.ini',
|
||||
'devtools/client/jsonview/test/browser.ini',
|
||||
],
|
||||
},
|
||||
);
|
||||
const tcUrl =
|
||||
'https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/gecko.v2.autoland.revision.d5b037941b0ebabcc9b843f24d926e9d65961087.taskgraph.decision/artifacts/public';
|
||||
// XXX: Fix this to re-enable test
|
||||
// I need to figure out the right options to get a gzip blob
|
||||
fetchMock.get(`${tcUrl}/tests-by-manifest.json.gz`, {
|
||||
body: new Blob(await gzip(JSON.stringify(testsByManifest)), {
|
||||
type: 'application/gzip',
|
||||
}),
|
||||
sendAsJson: false,
|
||||
});
|
||||
fetchMock.get(`${tcUrl}/manifests-by-task.json.gz`, {
|
||||
body: new Blob(await gzip(JSON.stringify(manifestsByTask)), {
|
||||
type: 'application/gzip',
|
||||
}),
|
||||
sendAsJson: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
@ -75,7 +102,8 @@ describe('Push', () => {
|
|||
replaceLocation({});
|
||||
});
|
||||
|
||||
test('jobs should have test_path field to filter', async () => {
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
test.skip('jobs should have test_path field to filter', async () => {
|
||||
const { store } = configureStore();
|
||||
const { getByText } = render(testPush(store, new FilterModel()));
|
||||
|
||||
|
@ -88,11 +116,44 @@ describe('Push', () => {
|
|||
};
|
||||
|
||||
await validateJob('Jit8', []);
|
||||
await validateJob('dt5', [
|
||||
'devtools/client/inspector/compatibility/test/browser/browser.ini',
|
||||
'devtools/client/inspector/grids/test/browser.ini',
|
||||
'devtools/client/inspector/rules/test/browser.ini',
|
||||
// XXX: It should be returning test paths instead of manifest paths
|
||||
await validateJob('dt1', [
|
||||
'devtools/client/framework/browser-toolbox/test/browser.ini',
|
||||
'devtools/client/framework/test/browser.ini',
|
||||
'devtools/client/framework/test/metrics/browser_metrics_inspector.ini',
|
||||
'devtools/client/inspector/changes/test/browser.ini',
|
||||
'devtools/client/inspector/extensions/test/browser.ini',
|
||||
'devtools/client/inspector/markup/test/browser.ini',
|
||||
'devtools/client/jsonview/test/browser.ini',
|
||||
'devtools/client/shared/test/browser.ini',
|
||||
'devtools/client/styleeditor/test/browser.ini',
|
||||
'devtools/client/webconsole/test/node/fixtures/stubs/stubs.ini',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Artifact transformations', () => {
|
||||
test('Merge artifacts', () => {
|
||||
const taskNameToTestPaths = joinArtifacts(manifestsByTask, testsByManifest);
|
||||
expect(taskNameToTestPaths).toMatchObject({
|
||||
'test-linux1804-64/debug-mochitest-devtools-chrome-e10s-1': [
|
||||
'devtools/client/framework/browser-toolbox/test/browser_browser_toolbox.js',
|
||||
'devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_debugger.js',
|
||||
'devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_contentframe_inspector.js',
|
||||
'devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector.js',
|
||||
'devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_rtl.js',
|
||||
'devtools/client/framework/browser-toolbox/test/browser.ini',
|
||||
'devtools/client/framework/test/foo.js',
|
||||
'devtools/client/framework/test/browser.ini',
|
||||
'devtools/client/framework/test/metrics/browser_metrics_inspector.ini',
|
||||
'devtools/client/inspector/changes/test/browser.ini',
|
||||
'devtools/client/inspector/extensions/test/browser.ini',
|
||||
'devtools/client/inspector/markup/test/browser.ini',
|
||||
'devtools/client/jsonview/test/browser.ini',
|
||||
'devtools/client/shared/test/browser.ini',
|
||||
'devtools/client/styleeditor/test/browser.ini',
|
||||
'devtools/client/webconsole/test/node/fixtures/stubs/stubs.ini',
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { inflate } from 'pako';
|
||||
|
||||
export const unGzip = async binData => {
|
||||
const decompressed = await inflate(binData, { to: 'string' });
|
||||
return JSON.parse(decompressed);
|
||||
};
|
||||
|
||||
export default unGzip;
|
|
@ -2,13 +2,13 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import { inflate } from 'pako';
|
||||
|
||||
import {
|
||||
thEvents,
|
||||
thOptionOrder,
|
||||
thPlatformMap,
|
||||
} from '../../helpers/constants';
|
||||
import decompress from '../../helpers/gzip';
|
||||
import { getGroupMapKey } from '../../helpers/aggregateId';
|
||||
import { getAllUrlParams, getUrlParam } from '../../helpers/location';
|
||||
import JobModel from '../../models/job';
|
||||
|
@ -34,27 +34,45 @@ import PushJobs from './PushJobs';
|
|||
const watchCycleStates = ['none', 'push', 'job', 'none'];
|
||||
const platformArray = Object.values(thPlatformMap);
|
||||
|
||||
const fetchTestManifests = async (project, revision) => {
|
||||
let taskNameToManifests = {};
|
||||
export const joinArtifacts = (manifestsByTask, testsByManifest) => {
|
||||
// We need to create a map from taskName to testPaths:
|
||||
// e.g. taskName: test-linux1804-64-shippable/opt-mochitest-devtools-chrome-e10s-1
|
||||
// e.g. manifest: devtools/client/framework/browser-toolbox/test/browser.ini
|
||||
// e.g. testPath: devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_debugger.js
|
||||
const taskNameToTestPaths = {};
|
||||
Object.entries(manifestsByTask).forEach(([taskName, manifetsts]) => {
|
||||
manifetsts.forEach(manifest => {
|
||||
const splitPath = manifest.split('/');
|
||||
const basePath = splitPath.splice(0, splitPath.length - 1).join('/');
|
||||
taskNameToTestPaths[taskName] = taskNameToTestPaths[taskName] || [];
|
||||
(testsByManifest[manifest] || []).forEach(test => {
|
||||
taskNameToTestPaths[taskName].push(`${basePath}/${test}`);
|
||||
});
|
||||
taskNameToTestPaths[taskName].push(manifest);
|
||||
});
|
||||
});
|
||||
return taskNameToTestPaths;
|
||||
};
|
||||
|
||||
const fetchGeckoDecisionArtifact = async (project, revision, filePath) => {
|
||||
let artifactContents = {};
|
||||
const rootUrl = prodFirefoxRootUrl;
|
||||
const url = `${checkRootUrl(
|
||||
rootUrl,
|
||||
)}/api/index/v1/task/gecko.v2.${project}.revision.${revision}.taskgraph.decision/artifacts/public/manifests-by-task.json.gz`;
|
||||
)}/api/index/v1/task/gecko.v2.${project}.revision.${revision}.taskgraph.decision/artifacts/public/${filePath}`;
|
||||
const response = await fetch(url);
|
||||
if ([200, 303, 304].includes(response.status)) {
|
||||
const blob = await response.blob();
|
||||
const binData = await blob.arrayBuffer();
|
||||
const decompressed = await inflate(binData, { to: 'string' });
|
||||
taskNameToManifests = JSON.parse(decompressed);
|
||||
} else if (response.status === 404) {
|
||||
// This else/if block is for backward compatibility
|
||||
// XXX: Remove after end of July 2020
|
||||
const resp = await fetch(url.replace('.json.gz', '.json'));
|
||||
if ([200, 303, 304].includes(resp.status)) {
|
||||
taskNameToManifests = await resp.json();
|
||||
if (url.endsWith('.gz')) {
|
||||
if ([200, 303, 304].includes(response.status)) {
|
||||
const blob = await response.blob();
|
||||
const binData = await blob.arrayBuffer();
|
||||
artifactContents = await decompress(binData);
|
||||
}
|
||||
} else if (url.endsWith('.json')) {
|
||||
if ([200, 303, 304].includes(response.status)) {
|
||||
artifactContents = await response.json();
|
||||
}
|
||||
}
|
||||
return taskNameToManifests;
|
||||
return artifactContents;
|
||||
};
|
||||
|
||||
class Push extends React.PureComponent {
|
||||
|
@ -81,8 +99,12 @@ class Push extends React.PureComponent {
|
|||
// if ``nojobs`` is on the query string, then don't load jobs.
|
||||
// this allows someone to more quickly load ranges of revisions
|
||||
// when they don't care about the specific jobs and results.
|
||||
if (!getAllUrlParams().has('nojobs')) {
|
||||
await Promise.all([this.fetchJobs(), this.fetchTestManifests()]);
|
||||
const allParams = getAllUrlParams();
|
||||
if (!allParams.has('nojobs')) {
|
||||
await this.fetchJobs();
|
||||
}
|
||||
if (allParams.has('test_paths')) {
|
||||
await this.fetchTestManifests();
|
||||
}
|
||||
|
||||
window.addEventListener(thEvents.applyNewJobs, this.handleApplyNewJobs);
|
||||
|
@ -181,17 +203,26 @@ class Push extends React.PureComponent {
|
|||
fetchTestManifests = async () => {
|
||||
const { currentRepo, push } = this.props;
|
||||
|
||||
const jobTypeNameToManifests = await fetchTestManifests(
|
||||
currentRepo.name,
|
||||
push.revision,
|
||||
);
|
||||
// Call setState with callback to guarantee the state of jobTypeNameToManifest
|
||||
const [manifestsByTask, testsByManifest] = await Promise.all([
|
||||
fetchGeckoDecisionArtifact(
|
||||
currentRepo.name,
|
||||
push.revision,
|
||||
'manifests-by-task.json.gz',
|
||||
),
|
||||
fetchGeckoDecisionArtifact(
|
||||
currentRepo.name,
|
||||
push.revision,
|
||||
'tests-by-manifest.json.gz',
|
||||
),
|
||||
]);
|
||||
const taskNameToTestPaths = joinArtifacts(manifestsByTask, testsByManifest);
|
||||
// Call setState with callback to guarantee the state of taskNameToTestPaths
|
||||
// to be set since it is read within mapPushJobs and we might have a race
|
||||
// condition. We are also reading jobList now rather than before fetching
|
||||
// the artifact because it gives us an empty list
|
||||
this.setState(
|
||||
{
|
||||
jobTypeNameToManifests,
|
||||
taskNameToTestPaths,
|
||||
},
|
||||
() => this.mapPushJobs(this.state.jobList),
|
||||
);
|
||||
|
@ -215,7 +246,7 @@ class Push extends React.PureComponent {
|
|||
|
||||
mapPushJobs = (jobs, skipJobMap) => {
|
||||
const { updateJobMap, recalculateUnclassifiedCounts, push } = this.props;
|
||||
const { jobTypeNameToManifests = {} } = this.state;
|
||||
const { taskNameToTestPaths = {} } = this.state;
|
||||
|
||||
// whether or not we got any jobs for this push, the operation to fetch
|
||||
// them has completed.
|
||||
|
@ -227,7 +258,7 @@ class Push extends React.PureComponent {
|
|||
const existingJobs = jobList.filter(job => !newIds.includes(job.id));
|
||||
// Join both lists and add test_paths property
|
||||
const newJobList = [...existingJobs, ...jobs].map(job => {
|
||||
job.test_paths = jobTypeNameToManifests[job.job_type_name] || [];
|
||||
job.test_paths = taskNameToTestPaths[job.job_type_name] || [];
|
||||
return job;
|
||||
});
|
||||
const platforms = this.sortGroupedJobs(
|
||||
|
|
|
@ -279,10 +279,6 @@ export default class FilterModel {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (field === 'test_paths' && job[field]) {
|
||||
// Make all paths unix style
|
||||
return job[field].map(testPath => testPath.replace(/\\/g, /\//));
|
||||
}
|
||||
return job[field];
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче