Skip to content

Commit

Permalink
Cache requested source maps
Browse files Browse the repository at this point in the history
This fixes #1245 [1].

[1] #1245
  • Loading branch information
badeball committed Oct 4, 2024
1 parent 1e11dca commit 8acb238
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project will be documented in this file.

## Unreleased

- Cache requested source maps, fixes [#1245](https://github.com/badeball/cypress-cucumber-preprocessor/discussions/1245).

## v21.0.1

- Support config file locations other than project root, fixes [#1243](https://github.com/badeball/cypress-cucumber-preprocessor/discussions/1243).
Expand Down
30 changes: 30 additions & 0 deletions features/issues/1245.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Feature: cached source maps

Ideally, I would have liked to test this in a more behavior driven way, like counting the number
of times that the bundler, whichever was configured, was invoked. However, it turns out that
Cypress does in fact cache [1]. Unfortunately, this cache is dead slow and requires higher-order
caching. The only way to verify that Cypress' cache isn't invoked, is by interpreting stderr.

[1] https://github.com/cypress-io/cypress/blob/v13.15.0/packages/server/lib/plugins/preprocessor.js#L94-L98

Scenario:
Given a file named "cypress/e2e/a.feature" with:
"""
Feature: a feature name
Scenario: a scenario name
Given a step
"""
And a file named "cypress/support/step_definitions/steps.ts" with:
"""
import { Given } from "@badeball/cypress-cucumber-preprocessor";
Given("a step", function(this: Mocha.Context) {});
for (let i = 0; i < 10; i++) {
Given(`an unused step (${i + 1})`, function(this: Mocha.Context) {});
};
"""
When I run cypress with environment variables
| name | value |
| DEBUG | cypress:server:preprocessor |
Then it passes
# Why two? Who knows. Cypress requests this file twice and the library once.
And I should see exactly 2 instances of "headless and already processed" in stderr
11 changes: 11 additions & 0 deletions features/step_definitions/cli_steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,14 @@ Then(
assert.match(expectLastRun(this).stdout, pendingScenarioExpr(scenarioName));
},
);

Then(
"I should see exactly {int} instance(s) of {string} in stderr",
function (this: ICustomWorld, expectedOccurences: number, output: string) {
const actualyOccurences =
expectLastRun(this).stderr.match(new RegExp(rescape(output), "g"))
?.length ?? 0;

assert.equal(actualyOccurences, expectedOccurences);
},
);
14 changes: 13 additions & 1 deletion lib/helpers/source-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ function sourceMapWarn(message: string) {
isSourceMapWarned = true;
}

const cache = new Map<string, string | undefined>();

/**
* Taken from https://github.com/evanw/node-source-map-support/blob/v0.5.21/source-map-support.js#L148-L177.
*/
Expand Down Expand Up @@ -61,14 +63,24 @@ export function retrieveSourceMapURL(source: string) {
return lastMatch[1];
}

export function cachedRetrieveSourceMapURL(source: string): string | undefined {
if (cache.has(source)) {
return cache.get(source);
} else {
const result = retrieveSourceMapURL(source);
cache.set(source, result);
return result;
}
}

export function maybeRetrievePositionFromSourceMap(): Position | undefined {
const stack = ErrorStackParser.parse(new Error());

if (stack[0].fileName == null) {
return;
}

const sourceMappingURL = retrieveSourceMapURL(stack[0].fileName);
const sourceMappingURL = cachedRetrieveSourceMapURL(stack[0].fileName);

if (!sourceMappingURL) {
return;
Expand Down

0 comments on commit 8acb238

Please sign in to comment.