Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return artifacts from runner and move assets/artifacts saving to cli #1163

Merged
merged 17 commits into from
Jan 4, 2017

Conversation

weiwei-lin
Copy link
Contributor

@weiwei-lin weiwei-lin commented Dec 14, 2016

Moving assets/artifacts saving to cli will allow us to host trace files in the future #616 (with #1130). Because the server needs to know where those trace files are but it's unnatural to include those file paths in the results returned by runner.
This will also make it possible to compare timeline data in Project Performance Experiment #1143.

Taking over from #1115

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

definitely liking this in the CLI instead of runner

@@ -67,6 +67,7 @@ const cliFlags = yargs
'mobile',
'save-assets',
'save-artifacts',
'exclude-artifacts',
Copy link
Member

Choose a reason for hiding this comment

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

we should try to pick a flag name with a default false value (so that undefined or anything falsy will be coerced to it). Maybe just --include-artifacts?

Copy link
Member

@brendankenny brendankenny Dec 15, 2016

Choose a reason for hiding this comment

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

I guess the question is, what does this flag observably do? At this point it won't affect the html report, so users need to choose json to see the artifacts in the output (and they could use --save-artifacts for that).

Maybe it makes sense not to have a CLI flag, and for now always clear results.artifacts in bin.ts. Then future PRs can add flags for their own behavior (like #1130 adding --view), which can control if the artifacts property is cleared or not.

That should help with later orthogonality of flags, so we don't have to figure out things like, what if the user sets --view and --include-artifacts=false

// Process URLs once at a time
const address = addresses.shift();
if (!address) {
return;
}

return lighthouse(address, flags, config)
.then((results: Results) => {
if (flags.saveArtifacts) {
return assetSaver.saveArtifacts(results.artifacts).then((_: Object) => results);
Copy link
Member

@brendankenny brendankenny Dec 15, 2016

Choose a reason for hiding this comment

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

assetSaver.saveArtifacts is sync, so won't return a promise (feel free to alter here or the function itself :)

})
.then((results: Results) => {
if (flags.saveAssets) {
return assetSaver.saveAssets({url: results.url}, results.artifacts).then((_:Object) => results);
Copy link
Member

Choose a reason for hiding this comment

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

assetSaver.saveAssets returns Promise<undefined>, so easier to do .then(() => results)

// Run each audit sequentially, the auditResults array has all our fine work
const auditResults = [];
run = run.then(artifacts => config.audits.reduce((chain, audit) => {
return chain.then(_ => {
return Runner._runAudit(audit, artifacts);
}).then(ret => auditResults.push(ret));
}, Promise.resolve()).then(_ => auditResults));
}, Promise.resolve()).then(_ => {
Copy link
Member

@brendankenny brendankenny Dec 15, 2016

Choose a reason for hiding this comment

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

can you re-indent this block (moving config.audits.reduce((chain, audit) => { from above to its own line) to make it clearer that artifacts is still in scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated this to use "for .. of loop" and make those audit runs become a "standard run" (i.e. resolves to artifacts). Hopefully, this will increase readability.

})
.then((results: Results) => {
if (flags.excludeArtifacts) {
delete results.artifacts;
Copy link
Member

@brendankenny brendankenny Dec 15, 2016

Choose a reason for hiding this comment

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

doing results.artifacts = undefined; here instead should be ok. Using delete tempts slow objects and we're about to do a bunch of processing on the results, and e.g. JSON.stringify drops undefined properties anyway

} else if (config.auditResults) {
// If there are existing audit results, surface those here.
run = run.then(_ => config.auditResults);
run = run.then(_ => {
return {artifacts: config.artifacts, auditResults: config.auditResults};
Copy link
Member

Choose a reason for hiding this comment

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

do you still want the default {} if there's no config.artifacts or is undefined ok in that case?

@@ -119,7 +108,9 @@ class Runner {

// Format and aggregate results before returning.
run = run
.then(auditResults => {
.then(auditData => {
Copy link
Member

Choose a reason for hiding this comment

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

doesn't really matter, but maybe runResults?

@@ -119,7 +108,9 @@ class Runner {

// Format and aggregate results before returning.
run = run
.then(auditResults => {
.then(auditData => {
const artifacts = auditData.artifacts;
Copy link
Member

Choose a reason for hiding this comment

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

nit: just inline these below rather than declaring separate variables

@brendankenny
Copy link
Member

should also add some simple tests to runner-test.js that artifacts produced by gatherers or passed in on config.artifacts get copied through runner. There are a few tests in there already demonstrating use of the driver mock, passing in artifacts in a config, etc.

@weiwei-lin
Copy link
Contributor Author

Fixed some bugs and coding style. Still working on test cases.

@weiwei-lin weiwei-lin closed this Dec 15, 2016
@weiwei-lin weiwei-lin deleted the flag-provide-artifacts branch December 15, 2016 10:45
@weiwei-lin weiwei-lin restored the flag-provide-artifacts branch December 15, 2016 10:45
@weiwei-lin weiwei-lin reopened this Dec 15, 2016
@weiwei-lin
Copy link
Contributor Author

Added test cases to ensure artifacts is returned when 1. auditResults is generated in Runner.run, 2. existing auditResults is given to Runner.run

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

more review :)

(sorry about the rebase)

// Process URLs once at a time
const address = addresses.shift();
if (!address) {
return;
}

return lighthouse(address, flags, config)
.then((results: Results) => {
if (flags.saveArtifacts) {
Copy link
Member

Choose a reason for hiding this comment

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

maybe combine these two saveArtifacts and saveAssets handlers into one then block? (and maybe even pull into a separate function? not sure about that one, though)

return results;
})
.then((results: Results) => {
results.artifacts = undefined;
Copy link
Member

Choose a reason for hiding this comment

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

should add a comment explaining why we're doing this. Maybe something simple like // don't include artifacts in printed output by default. or something?

// Process URLs once at a time
const address = addresses.shift();
if (!address) {
return;
}

return lighthouse(address, flags, config)
.then((results: Results) => {
Copy link
Member

Choose a reason for hiding this comment

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

sorry, will have to rebase after #1141

});

return Runner.run({}, {url, config}).then(results => {
assert.ok(results.artifacts !== undefined);
Copy link
Member

Choose a reason for hiding this comment

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

maybe check that results.artifacts.HTTPS is still on there? Do we want to be stricter with this check in any other way?

@@ -295,4 +295,56 @@ describe('Runner', () => {
assert.strictEqual(AuditClass.meta.name, auditExpectedName);
});
});

it('returns artifacts when audits are given', () => {
Copy link
Member

Choose a reason for hiding this comment

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

maybe something like 'results include artifacts when audits are given'?

});
});

it('returns artifacts when auditResult is given', () => {
Copy link
Member

Choose a reason for hiding this comment

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

same thing, something like 'results include artifacts when auditResult is given'?

return Runner.run(null, {url, config, driverMock}).then(results => {
assert.ok(results.artifacts !== undefined);
});
});
Copy link
Member

Choose a reason for hiding this comment

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

should probably have one more with passes and audits in the config file. driverMock will provide a trace, so could do a pass that records a trace and then an audit that purely requires a trace (maybe user-timings?). Something like

const config = new Config({
  passes: [{
    recordTrace: true,
    gatherers: []
  }],
  audits: [
    'user-timings'
  ]
});

and then the result should have a results.artifacts.traces array with length > 0

That's kind of venturing into integration testing (relying on gather-runner internally, for one), but good to catch this case because most lighthouse runs are with configs of the passes/audits format. What do you think?

} else if (config.auditResults) {
// If there are existing audit results, surface those here.
run = run.then(_ => config.auditResults);
// Instantiate and return artifacts for consistency.
// Although we can move this upwards to reduce code repetition, this process might be quite
Copy link
Member

@brendankenny brendankenny Dec 16, 2016

Choose a reason for hiding this comment

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

the // Although... comment lines probably aren't necessary here (though good for the code reviewer :).

I do think you're right we want to have the same object shape for artifacts regardless of path through here, so adding the computed artifacts is the right move

return Printer.write(results, 'html', filePath).then(() => {
opn(`http://localhost:${port}/reports/${filename}`);
});
const filename = `${assetSaver.getFilenamePrefix({url})}.report.html`;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although this line can be combined with line 284, we need a variable outside of the 'then-chain' to do that. Also, since we no longer use symlinks, the filenames do not need to be the same. And in the future, we may save JSON instead of html.

continue;
}
assert.ok(results.artifacts.hasOwnProperty(method));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

assetSaver needs computedArtifacts to save screenshots.

if (flags.output === Printer.OutputMode[Printer.OutputMode.pretty]) {
Printer.write(results, 'html', filename);
const filename = `${assetSaver.getFilenamePrefix({url})}.report.html`;
return Printer.write(results, 'html', filename);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed a concurrency error here. Previously, promise will resolve to next 'then' before Printer finishes writing. No sure if that matters.

@weiwei-lin weiwei-lin force-pushed the flag-provide-artifacts branch 2 times, most recently from db32b77 to 23a5483 Compare December 21, 2016 04:08
@weiwei-lin
Copy link
Contributor Author

Thanks for the advice. Ready for review again! @brendankenny PTAL.

Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

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

Yup this works well for me.
Only one nit, otherwise I think this is good to go.

Will merge after the nit is addressed. :)

return results;
})
.then((results: Results) => {
// remove artifacts so users won't get unnecessary artifacts in the report.
Copy link
Member

Choose a reason for hiding this comment

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

let's just do this last bit in the previous .then(), since its all related

Copy link
Contributor Author

@weiwei-lin weiwei-lin Jan 4, 2017

Choose a reason for hiding this comment

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

Edit: I will come back to this after I rebased this branch
Edit: Fixed. Thanks for the advice

Weiwei Lin and others added 13 commits January 4, 2017 17:11
Runner now returns artifacts.\nAdded --exclude-aritifacts, default to be true, will delete artifacts after runner returns it\n
Now artifacts are always deleted/undeined before passing to Printer.\nNo longer use .then() after assetSaver.saveArtifacts since it's sync.\nChanged audit run in Runner.run to use for .. of loop instead of reduce to increase  readability.
also made runner always return instantiated artifacts for consistency
@weiwei-lin
Copy link
Contributor Author

Yup this works well for me.
Only one nit, otherwise I think this is good to go.

Will merge after the nit is addressed. :)

Ready for review again. PTAL @paulirish.

config: Object): Promise<undefined> {

let chromeLauncher: ChromeLauncher;
return initPort(flags)
.then(() => getDebuggableChrome(flags))
.then(chrome => chromeLauncher = chrome)
.then(() => lighthouse(url, flags, config))
.then((results: Results) => {
// delte artifacts from result so reports won't include artifacts.
Copy link
Member

Choose a reason for hiding this comment

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

delte => delete

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed.

return results;
})
.then((results: Results) => {
return results;
Copy link
Member

Choose a reason for hiding this comment

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

i believe you can nuke this entire .then() now..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops. Sorry, wasn't careful enough when checking after rebase. Didn't notice that. Already fixed.

config: Object): Promise<undefined> {

let chromeLauncher: ChromeLauncher;
return initPort(flags)
.then(() => getDebuggableChrome(flags))
.then(chrome => chromeLauncher = chrome)
.then(() => lighthouse(url, flags, config))
.then((results: Results) => {
// delte artifacts from result so reports won't include artifacts.
const artifacts = results.artifacts;
Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

:)

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

some small last things. I like this refactor

});

return Runner.run({}, {url, config}).then(results => {
if (!results.artifacts instanceof Object) {
Copy link
Member

Choose a reason for hiding this comment

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

not sure if these instanceof checks are really necessary. If not an object, it will throw on the property accesses below anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the advice. Fixed.

assert.strictEqual(results.artifacts.HTTPS.value, true);

for (const method in computedArtifacts) {
if (!computedArtifacts.hasOwnProperty(method)) {
Copy link
Member

Choose a reason for hiding this comment

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

could you just do a loop over Object.keys(computedArtifacts) here to avoid hasOwnProperty, etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the advice. Fixed.

});

return Runner.run(null, {url, config, driverMock}).then(results => {
if (!results.artifacts instanceof Object) {
Copy link
Member

Choose a reason for hiding this comment

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

same as above

assert.ok(false);
return;
}

Copy link
Member

Choose a reason for hiding this comment

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

no results.artifacts.HTTPS here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No sure what you mean here. Do you mean I need to check whether results.artifacts.HTTPS exists?
If that's the case, do we need to check results.artifacts as well? And also results.audits and so on in other test cases?

Copy link
Member

Choose a reason for hiding this comment

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

I mean in the other two tests you check assert.strictEqual(results.artifacts.HTTPS.value, true);. I'm not sure what the value of results.artifacts.HTTPS.value will be here, but I believe runner will attempt to run the HTTPS gatherer with the mock driver supplied, so it seems like it's worth keeping the check in there since this is checking the artifacts coming through

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it. And added assert.ok(results.artifacts.HTTPS) to check whether non-computed artifacts attributes are returned.

return;
}

for (const method in computedArtifacts) {
Copy link
Member

Choose a reason for hiding this comment

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

same as above

});

return Runner.run(null, {url, config, driverMock}).then(results => {
if (!results.artifacts instanceof Object) {
Copy link
Member

Choose a reason for hiding this comment

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

and same here and the for..in loop below too :)

.then((results: Results) => {
// delete artifacts from result so reports won't include artifacts.
const artifacts = results.artifacts;
results.artifacts = undefined;
Copy link
Member

Choose a reason for hiding this comment

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

style nit: maybe add a line break after this line to separate the two actions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. Fixed.

return Runner.run({}, {url, config}).then(results => {
assert.strictEqual(results.artifacts.HTTPS.value, true);

for (const method in Object.keys(computedArtifacts)) {
Copy link
Member

Choose a reason for hiding this comment

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

I think you want for...of here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What a dumb mistake : (
Fixed.

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

Really like how this change turned out!

@brendankenny
Copy link
Member

thanks for all your patience :)

@brendankenny brendankenny merged commit 30f1344 into GoogleChrome:master Jan 4, 2017
@weiwei-lin weiwei-lin deleted the flag-provide-artifacts branch January 4, 2017 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants