Skip to content

feat(selectivity): implement fs-caching#1204

Merged
KuznetsovRoman merged 1 commit intomasterfrom
TESTPLANE-883.selectivity_fs_caching
Feb 13, 2026
Merged

feat(selectivity): implement fs-caching#1204
KuznetsovRoman merged 1 commit intomasterfrom
TESTPLANE-883.selectivity_fs_caching

Conversation

@KuznetsovRoman
Copy link
Member

@KuznetsovRoman KuznetsovRoman commented Feb 11, 2026

Caching selectivity assets on event "styleSheetAdded" or "scriptParsed"

  • we check for existing cache, If it is cached, selectivity just acknowledges cache presence and does nothing extra.
  • we check for existing cache, if it is NOT cached, its received via CDP or directly from node.js, then cached, and marked as "cached on fs"
  • if source map is inlined, its not cached.

Caching on "stop record"

  • If we encounter fs-cached asset, its read from fs

  • If its not fs-cached and was not handled during test run, its received via CDP or directly from node.js, then cached and stored in RAM because we will use it right now

  • Testplane runtime dependencies are cached as well

  • "Testplane runtime dependencies" and "selectivity assets" are stored with different prefix in order to reduce chance of matching hashes

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 11, 2026

Open in StackBlitz

npm i https://pkg.pr.new/gemini-testing/testplane@1204

commit: 665e4df

@KuznetsovRoman KuznetsovRoman force-pushed the TESTPLANE-883.selectivity_fs_caching branch from b86f073 to 18ef7b4 Compare February 11, 2026 04:12
Copy link
Member Author

Choose a reason for hiding this comment

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

used to read/write fs cache when multiple threads read/write the same cache
flag file (empty file with '-ready' prefix) is used as mark of successful cache write

Copy link
Member

@shadowusr shadowusr left a comment

Choose a reason for hiding this comment

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

Great additions overall! 🔥

Another important case I'd consider is to analyze if all cache-related errors are wrapped. Right now it feels like there are definitely places where we can get critical runtime errors, but would be better to just get "cache miss" & refetch. For example:

};

const ensurePosixRelativeDependencyPathExists = memoize((posixRelativePath: string): void => {
export const isDataProtocol = (fileUrlLikePath: string): boolean => getProtocol(fileUrlLikePath) === "data:";
Copy link
Member

Choose a reason for hiding this comment

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

Hmmm this doesn't look correct, because getProtocol looks for :// in string, but data URLs don't have those?

Looking at examples on MDN:

data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==


export type CachedOnFs = true;
export type ActualValue = string;
export type SelectivityAssetState = Promise<
Copy link
Member

Choose a reason for hiding this comment

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

I prefer a more explicit object shapes like

enum AssetStatus {
    CachedOnFs,
    Loaded,
    Errored,
    Missing
}

interface CachedAsset {
    status: AssetStatus.CachedOnFs;
}

interface LoadedAsset {
    status: AssetStatus.Loaded;
    data: string;
}

// ...

type Asset = CachedAsset | LoadedAsset /* ... */;

This usually makes the code more explicit, readable, type-safe and easier to work with. For example, with current shape you've already pretty much exhausted what you can store in such objects, but what if you'll need to add another type of asset state which has string value too, but it's not an actual value? There's no convenient way to do that. With discriminated union, we have infinite options.

Not critical to me

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, its kind of quirky, but i wanted to avoid extra heap allocations and memory usage overall
Memory footprint here might seem low, but with large amount of workers and parallel tests and test assets, it adds up.

Without selectivity:
image

With selectivity:
image

Its memory usage when only "true" is stored for each asset as they are all fs-cached, and besides it, i dont see where selectivity uses extra RAM)

.getScriptSource(this._sessionId, scriptId)
.then(res => res.scriptSource)
.then(data => setCachedSelectivityFile(CacheType.Asset, url, data))
.then(() => true as const)
Copy link
Member

Choose a reason for hiding this comment

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

Hmmm one thing is a bit concerning to me over here:

  • setCachedSelectivityFile doesn't provide a way to know if a cache write was successful, it can silently fail
  • But we mark it as successful here anyways (by writing true)
  • This leads to incorrect state: we consider cache to be successfully written on fs, but in fact it doesn't exist. Right now the stop() call would fail in this case with ENOENT when trying to read the source

Can this truly happen or am i missing something?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm, if "write" actually fails, then yeah, it can definitely happen.
It probably wont, because other test might check "cache does not exist" and fetch it, but it can.

I will fix it by removing ignoring error in "setCachedSelectivityFile" and handling it here so we would store actual value in RAM if writing to fs-cache fails

@KuznetsovRoman KuznetsovRoman force-pushed the TESTPLANE-883.selectivity_fs_caching branch 4 times, most recently from 4f6fd50 to 7e6a6b4 Compare February 13, 2026 04:18
@KuznetsovRoman KuznetsovRoman force-pushed the TESTPLANE-883.selectivity_fs_caching branch from 7e6a6b4 to 665e4df Compare February 13, 2026 05:01
@KuznetsovRoman KuznetsovRoman merged commit 18cd769 into master Feb 13, 2026
7 checks passed
@KuznetsovRoman KuznetsovRoman deleted the TESTPLANE-883.selectivity_fs_caching branch February 13, 2026 05:08
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.

2 participants

Comments