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

core(tsc): add type checking to runner #4961

Merged
merged 5 commits into from
Apr 13, 2018
Merged

core(tsc): add type checking to runner #4961

merged 5 commits into from
Apr 13, 2018

Conversation

brendankenny
Copy link
Member

also adds result.d.ts to type the full LHR and adds more types to config.d.ts (though it still needs a lot of work in combo with typing config.js).

* @param {LH.Audit.Context} context
* @return {LH.Audit.Product | Promise<LH.Audit.Product>}
*/
static audit(artifacts, context) {
Copy link
Member Author

Choose a reason for hiding this comment

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

had to add this to the base class so typescript could see it in runner :)

// If status is good, content must be not null.
if (content === null) {
throw new Error(`Status ${status} was valid, but content was null`);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

@kdzwinel had to add this to keep the type checker happy even though it appears to not be possible. I couldn't think of a good way around this short of adding another property that lets you demonstrate to the type checker that a null content can't happen in certain cases, e.g.
{error: false, status: number, content: string}|{error: true, status: number|null, content: string|null}
...or we can just test like here for // can't ever happen :)

@brendankenny
Copy link
Member Author

runner.js is a lot easier to review at https://github.com/GoogleChrome/lighthouse/pull/4961/files?w=1

@@ -27,21 +26,6 @@ function assertTraceEventsEqual(traceEventsA, traceEventsB) {

/* eslint-env mocha */
describe('asset-saver helper', () => {
it('generates HTML', () => {
Copy link
Member Author

Choose a reason for hiding this comment

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

tested by "screenshots html file saved to disk with data" below

@@ -78,15 +61,15 @@ describe('asset-saver helper', () => {
const ssHTMLFilename = 'the_file-0.screenshots.html';
const ssFileContents = fs.readFileSync(ssHTMLFilename, 'utf8');
assert.ok(/<!doctype/gim.test(ssFileContents));
const expectedScreenshotContent = '{"timestamp":674089419.919';
const expectedScreenshotContent = '{"timestamp":668545858.596';
Copy link
Member Author

Choose a reason for hiding this comment

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

now really pulling screenshots from the trace instead of having requestScreenshots resolve on the saved screenshots, which turned out weren't from this trace :)

@@ -522,14 +520,10 @@ describe('Runner', () => {

return Runner.run({}, {url, config}).then(results => {
assert.deepEqual(results.artifacts.ViewportDimensions, ViewportDimensions);

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

Choose a reason for hiding this comment

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

artifacts from Runner.run() no longer include computed artifact methods on them. This and below just removes the check that they are on the returned artifacts object

@@ -16,14 +16,14 @@ const clampTo2Decimals = val => Math.round(val * 100) / 100;
class ReportScoring {
/**
* Computes the weighted-average of the score of the list of items.
* @param {!Array<{score: number, weight: number|undefined}>} items
* @param {Array<{score: number, weight: number}>} items
Copy link
Member Author

@brendankenny brendankenny Apr 13, 2018

Choose a reason for hiding this comment

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

turns out we always have score and weight by this point, so letting the type system handle it now

*
* @param {LH.Artifacts} artifacts
* @param {LH.Audit.Context} context
* @return {LH.Audit.Product | Promise<LH.Audit.Product>}
Copy link
Member

Choose a reason for hiding this comment

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

satisfying to have this documented. :)

/**
* The full output of a Lighthouse run.
*/
export interface Result {
Copy link
Member

Choose a reason for hiding this comment

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

rename file lhr.d.t.s

Copy link
Member Author

Choose a reason for hiding this comment

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

done

*/
export interface Result {
/**
* The URL that was supplied to Lighthouse and initially navigated to.
Copy link
Member

Choose a reason for hiding this comment

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

use single line for these. :)

Copy link
Member Author

Choose a reason for hiding this comment

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

done

/**
* Description of the runtime configuration used for gathering these results.
*/
runtimeConfig: Result.RuntimeConfig;
Copy link
Member

Choose a reason for hiding this comment

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

lets add a bit more whitespace (and even a comment?) to separate lhr-non-lite

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@@ -30,15 +30,6 @@ describe('ReportScoring', () => {
{score: 0.2, weight: 1},
]), 0.04);
});

it('should work for missing values', () => {
Copy link
Member

Choose a reason for hiding this comment

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

yeah... :) we assert before here that things have a score so I agree with not worrying about this case.

}

/**
* A LH Artifacts object, but the traces and devtoolsLogs are replaced
Copy link
Member

Choose a reason for hiding this comment

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

can u add a comment that this is present to support a legacy usecase. and link to the docs

was confused for a sec why runner artifacts were in here. :)

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Copy link
Collaborator

Choose a reason for hiding this comment

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

can we agree to nuke this use case before v3? it's a lot of config complexity for no known customers other than our tests

Copy link
Member

Choose a reason for hiding this comment

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

yup. we discussed yesterday that this is basically solved via
settings: { auditMode: 'path/to/artifacts/' }

patrick, that in line with your config/settings harmonization?

Copy link
Collaborator

Choose a reason for hiding this comment

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

yup exactly 👍

* @param {{config: LH.Config, url: string, initialUrl?: string, driverMock?: Driver}} opts
* @return {Promise<RunnerResult|undefined>}
*/
static async run(connection, opts) {
Copy link
Member Author

@brendankenny brendankenny Apr 13, 2018

Choose a reason for hiding this comment

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

opts now never leaves run() and _gatherArtifactsFromBrowser(), so we have a lot more freedom to do things like refactor our url/initialUrl/finalUrl business (not to mention options.settings (now isolated to _gatherArtifactsFromBrowser()) vs options.config.settings, etc)

Copy link
Member

Choose a reason for hiding this comment

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

very impressed with your commitment to appropriately back-ticking tokens in this sentence.
👨‍💻 ⌨️ 🥇

* Reference to an audit member of a category and how its score should be
* weighted and how its results grouped with other members.
*/
export interface CategoryMemberJson {
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 LH.Result.Category.CategoryMember

can we avoid defining it twice?

Copy link
Member Author

Choose a reason for hiding this comment

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

can we avoid defining it twice?

this is mostly mental organization that can be cleaned up when we add types to config.js. I'm not sure what we'll need by the end of things, so it was easier to keep separate for now.

Even if they end up exactly the same, we may still want two separate type names, one for acceptable input to config.js and one for what comes out. @patrickhulce mentions he uses something like typename and typenameUnsafe, and something like that might make it more obvious what the difference is. Though hopefully in our case you'll never see a *Json type inside of anything called by runner

Copy link
Member

@paulirish paulirish Apr 13, 2018

Choose a reason for hiding this comment

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

okay gotcha. can we do a comment like

Object shape maintained all the way to LH.Result.Category.CategoryMember

Copy link
Member Author

Choose a reason for hiding this comment

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

oh, I guess that's the answer to why there's a LH.Config.CategoryMember and a LH.Config.CategoryMemberJson.

For why there's an identical LH.Result.CategoryMember, I'm not sure what the best thing to do is. We don't have to define it twice (or three times :), but it does mean that if we add a property to one and not the other in the future we can just do that (instead of splitting the definition later and having to figure out which one we meant wherever we refer to it). I'm also not sure which name should win.

Copy link
Member Author

Choose a reason for hiding this comment

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

added a comment and TODO

Copy link
Member

Choose a reason for hiding this comment

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

okay. I think the resolution here is that we ideally split Config and ConfigJSON into two .d.ts files. (and I'd favor a clearer module split for all the JSON buddies to keep them separate from what new Config() returns. \o/

audits: CategoryMemberJson[];
}

export interface GroupJson {
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 LH.Result.Group

}

// For now, these are unchanged from JSON version
export interface CategoryMember extends CategoryMemberJson {}
Copy link
Member

Choose a reason for hiding this comment

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

in addition to the doubled-up type (LH.Result.Category.CategoryMember), this has two names?

* @param {!LH.ConfigSettings} settings
* @return {!Object} runtime config
* @param {LH.Config.Settings} settings
* @return {LH.Result.RuntimeConfig}
Copy link
Member Author

Choose a reason for hiding this comment

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

it's possible we want to just move this to the report generator now that we have more readable throttling information in config settings?

Copy link
Member

Choose a reason for hiding this comment

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

agree. not much left in this fn.

Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah, we should just output the settings used into the LHR :D

Copy link
Member Author

Choose a reason for hiding this comment

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

added to #4333

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.

Runner feels great. Nice!
I'm into the changes in scoring.

The instantiateComputedArtifacts change is quite nice too. As discussed, this sets up nicely to move our request* methods onto Audit and off artifacts in the future. :D

static _saveArtifacts(artifacts, path) {
return assetSaver.saveArtifacts(artifacts, path);
const driver = runnerOpts.driverMock || new Driver(connection);
const options = {
Copy link
Member

Choose a reason for hiding this comment

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

I see you already renamed opts to runnerOpts, though in the parent context is still has the opts name.

Regardless, feels like we can do gatherOpts for this guy. eviscerate all the options. woo!

Copy link
Member Author

Choose a reason for hiding this comment

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

boom done

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

this is great! made a few comments, but overall lgtm

@@ -162,9 +162,13 @@ async function prepareAssets(artifacts, audits) {
for (const passName of passNames) {
const trace = artifacts.traces[passName];
const devtoolsLog = artifacts.devtoolsLogs[passName];

// Avoid Runner->AssetSaver->Runner circular require by loading Runner here.
Copy link
Collaborator

Choose a reason for hiding this comment

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

:(

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, really disappointed to get there and run into that. Hopefully with computedArtifacts refactor that'll get cleaned up

try {
parsedURL = new URL(opts.url);
} catch (e) {
const err = new Error('The url provided should have a proper protocol and hostname.');
Copy link
Collaborator

Choose a reason for hiding this comment

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

can probably just throw directly here

* @param {!LH.ConfigSettings} settings
* @return {!Object} runtime config
* @param {LH.Config.Settings} settings
* @return {LH.Result.RuntimeConfig}
Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah, we should just output the settings used into the LHR :D

categories?: Record<string, Config.Category>;
groups?: Record<string, Config.Group>;
// TODO(bckenny): should be Partial<> maybe? don't want to fail imported json
artifacts?: Artifacts;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Partial sgtm now 👍

}

/**
* A LH Artifacts object, but the traces and devtoolsLogs are replaced
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we agree to nuke this use case before v3? it's a lot of config complexity for no known customers other than our tests

/**
* A description of configuration used for gathering.
*/
export interface RuntimeConfig {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah let's totally replace this with settings and have report renderer handle it

@brendankenny
Copy link
Member Author

I'll follow up with the artifacts removal. Any other feedback?

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.

4 participants