Skip to content

test(amber): add HDFSRecordStorageSpec for the hdfs record-storage arm - #7100

Merged
aglinxinyuan merged 1 commit into
apache:mainfrom
aglinxinyuan:test-hdfs-record-storage
Jul 30, 2026
Merged

test(amber): add HDFSRecordStorageSpec for the hdfs record-storage arm#7100
aglinxinyuan merged 1 commit into
apache:mainfrom
aglinxinyuan:test-hdfs-record-storage

Conversation

@aglinxinyuan

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

HDFSRecordStorage was the only member of the record-storage family without a spec, and the hdfs:// arm of SequentialRecordStorage.getStorage was unexercised too:

Class Spec before Spec now
EmptyRecordStorage
SequentialRecordStorage ✅ (None / file:// arms) ✅ + hdfs:// arm
VFSRecordStorage
HDFSRecordStorage none new

SequentialRecordStorageSpec had explicitly given up on it — "the hdfs:// branch is unit-test-hostile (HDFSRecordStorage's constructor calls FileSystem.get, which can block on DNS / network)". It turns out to be testable with no NameNode and no native Hadoop.

How. The class builds its own Configuration internally, so a test cannot inject one. But Hadoop's FileSystem cache is keyed on (scheme, authority, UGI) only — the Configuration's contents are not part of the key. So the spec gets there first:

test:  FileSystem.get("hdfs://<uuid>:9000/", conf)      conf: fs.hdfs.impl = WinutilsFreeLocalFileSystem
              │                                          └─▶ populates FileSystem.CACHE under (hdfs, <uuid>:9000, ugi)
              ▼
prod:  new HDFSRecordStorage(uri) ─▶ FileSystem.get(uri, new Configuration())
                                          └─▶ cache hit: same instance, fs.hdfs.impl never re-resolved,
                                              no NameNode contacted

That is the ordinary supported FileSystem.get path — no reflection anywhere. A unique authority per test means a unique cache key per test, so tests cannot see each other's instance or working directory, and each closes its FileSystem in a finally (which evicts only its own key, leaving the JVM-global cache as found).

WinutilsFreeLocalFileSystem is required, not incidental: plain LocalFileSystem dies at the constructor's first mkdirs on a winutils-less Windows host (RawLocalFileSystem.setPermissionShell.getWinUtilsPath"Hadoop bin directory does not exist"). The shim skips permission ops only on that host and otherwise delegates to Hadoop's default local filesystem, so this spec runs unchanged on Linux/macOS CI.

Covered: the scheme guard; the auto-create-folder branch; writer/reader round-tripping through the production serde; distinct files kept independent; re-opened writer overwriting rather than appending; the EmptyRecordStorage fallback for an absent file (asserted empty, not assumed); deleteStorage both present and absent; containsFolder for a directory vs a plain file vs nothing (the plain-file case is what an exists-only implementation gets wrong); and factory dispatch for both hdfs:// and HDFS:// (which pins both .toLowerCase call sites).

Also in this PR: the stale paragraph in SequentialRecordStorageSpec is corrected to point at the new spec. Its second claim was never true — it said an hdfs regression "would surface immediately in higher-level checkpoint / fault-tolerance suites that use hdfs:// URIs", but no suite in the repo uses an hdfs:// URI.

Two branches are left uncovered on purpose, noted in-file: a scheme-less URI NPEs on getScheme.toLowerCase before the assert evaluates (a latent production nit, not behaviour to cement), and the dfs.client…replace-datanode-on-failure config is unobservable without a real DataNode write pipeline.

Unlike its siblings, this spec restores AmberRuntime's _actorSystem / _serde in afterAll instead of nulling them, and builds its ActorSystem lazily. sbt runs amber's suites concurrently in one JVM and several install their own serde into those same globals; nulling makes a sibling's AmberRuntime.serde take its lazy-bootstrap branch mid-run and spin up an untracked cluster ActorSystem, and a strict val leaks one whenever a filter selects no test from the suite (as AMBER_TEST_FILTER does in the integration job).

Any related issues, documentation, discussions?

Closes #7098

How was this PR tested?

One new spec, 14 tests. Run together with all three existing sibling specs to confirm the AmberRuntime save/restore change does not disturb them — 43 tests total, Java 17:

sbt "WorkflowExecutionService/testOnly org.apache.texera.amber.engine.common.storage.HDFSRecordStorageSpec org.apache.texera.amber.engine.common.storage.SequentialRecordStorageSpec org.apache.texera.amber.engine.common.storage.VFSRecordStorageSpec org.apache.texera.amber.engine.common.storage.EmptyRecordStorageSpec"
[info] Suites: completed 4, aborted 0
[info] Tests: succeeded 43, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.

Verified locally on Windows with no winutils.exe and no hadoop.dll — the run logs Hadoop's own Did not find winutils.exe probe at WARN, which is exactly the failure plain LocalFileSystem would have thrown. Test/scalafmtCheck and Test/scalafix --check both [success]. No production file is touched.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 5)

HDFSRecordStorage was the only member of the record-storage family with
no spec (its three siblings are 85-100% covered), and the hdfs:// arm of
SequentialRecordStorage.getStorage was likewise unexercised.

The class builds its own Configuration internally, so a test cannot
inject one. The spec instead gets to the Hadoop FileSystem cache first:
the cache key is (scheme, authority, UGI) only -- Configuration contents
are not part of it -- so calling FileSystem.get on an hdfs:// URI with
fs.hdfs.impl set to WinutilsFreeLocalFileSystem, before constructing the
class under test, makes the internal FileSystem.get resolve to that
instance. No reflection, no NameNode, and a unique authority per test
keeps the cache entries isolated. The shim delegates to Hadoop's default
local filesystem off Windows, so this runs unchanged in CI.

Covers the scheme guard, the auto-create-folder branch, writer/reader
round-tripping through the production serde, the EmptyRecordStorage
fallback for an absent file, deleteStorage present and absent,
containsFolder for a directory vs a plain file vs nothing, and the
factory dispatch for both hdfs:// and HDFS://.

Also updates the stale paragraph in SequentialRecordStorageSpec that
claimed the hdfs arm was untestable and that regressions there would
surface in higher-level suites -- no suite uses an hdfs:// URI.
@github-actions

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • No candidates found from git blame history.

@aglinxinyuan
aglinxinyuan requested a review from mengw15 July 30, 2026 02:42
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 2 better · 🔴 5 worse · ⚪ 8 noise (<±5%) · 0 without baseline

Compared against main f28d8ec benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

config throughput MB/s latency max Δ latest / 7d
🔴 bs=10 sw=10 sl=64 405 0.247 23,971/29,319/29,319 us 🟢 -13.7% / 🔴 +94.8%
bs=100 sw=10 sl=64 797 0.486 122,670/157,275/157,275 us ⚪ within ±5% / 🔴 +46.5%
🔴 bs=1000 sw=10 sl=64 910 0.556 1,093,876/1,235,072/1,235,072 us 🔴 +9.1% / 🔴 +20.2%
Baseline details

Latest main f28d8ec from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 405 tuples/sec 443 tuples/sec 786.12 tuples/sec -8.6% -48.5%
bs=10 sw=10 sl=64 MB/s 0.247 MB/s 0.271 MB/s 0.48 MB/s -8.9% -48.5%
bs=10 sw=10 sl=64 p50 23,971 us 22,809 us 12,305 us +5.1% +94.8%
bs=10 sw=10 sl=64 p95 29,319 us 33,970 us 15,774 us -13.7% +85.9%
bs=10 sw=10 sl=64 p99 29,319 us 33,970 us 18,978 us -13.7% +54.5%
bs=100 sw=10 sl=64 throughput 797 tuples/sec 818 tuples/sec 999.71 tuples/sec -2.6% -20.3%
bs=100 sw=10 sl=64 MB/s 0.486 MB/s 0.499 MB/s 0.61 MB/s -2.6% -20.4%
bs=100 sw=10 sl=64 p50 122,670 us 118,550 us 100,616 us +3.5% +21.9%
bs=100 sw=10 sl=64 p95 157,275 us 150,568 us 107,356 us +4.5% +46.5%
bs=100 sw=10 sl=64 p99 157,275 us 150,568 us 113,255 us +4.5% +38.9%
bs=1000 sw=10 sl=64 throughput 910 tuples/sec 912 tuples/sec 1,031 tuples/sec -0.2% -11.8%
bs=1000 sw=10 sl=64 MB/s 0.556 MB/s 0.557 MB/s 0.63 MB/s -0.2% -11.7%
bs=1000 sw=10 sl=64 p50 1,093,876 us 1,094,906 us 980,328 us -0.1% +11.6%
bs=1000 sw=10 sl=64 p95 1,235,072 us 1,132,422 us 1,027,528 us +9.1% +20.2%
bs=1000 sw=10 sl=64 p99 1,235,072 us 1,132,422 us 1,054,298 us +9.1% +17.1%
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,493.83,200,128000,405,0.247,23971.17,29319.48,29319.48
1,100,10,64,20,2509.29,2000,1280000,797,0.486,122670.44,157275.31,157275.31
2,1000,10,64,20,21968.61,20000,12800000,910,0.556,1093876.23,1235071.83,1235071.83

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.43%. Comparing base (226614b) to head (6cf3db5).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #7100      +/-   ##
============================================
+ Coverage     79.39%   79.43%   +0.04%     
- Complexity     3801     3814      +13     
============================================
  Files          1159     1159              
  Lines         46150    46150              
  Branches       5129     5129              
============================================
+ Hits          36641    36661      +20     
+ Misses         7888     7866      -22     
- Partials       1621     1623       +2     
Flag Coverage Δ *Carryforward flag
access-control-service 70.00% <ø> (ø) Carriedforward from 226614b
agent-service 77.42% <ø> (ø) Carriedforward from 226614b
amber 72.72% <ø> (+0.11%) ⬆️
computing-unit-managing-service 20.49% <ø> (ø) Carriedforward from 226614b
config-service 66.66% <ø> (ø) Carriedforward from 226614b
file-service 67.21% <ø> (ø) Carriedforward from 226614b
frontend 83.04% <ø> (ø) Carriedforward from 226614b
notebook-migration-service 78.94% <ø> (ø) Carriedforward from 226614b
pyamber 97.38% <ø> (ø) Carriedforward from 226614b
workflow-compiling-service 26.31% <ø> (ø) Carriedforward from 226614b

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mengw15 mengw15 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds unit tests for the hdfs:// record-storage path in Amber by introducing a dedicated spec for HDFSRecordStorage and updating existing commentary to reflect that the hdfs factory-dispatch arm is now covered.

Changes:

  • Added HDFSRecordStorageSpec to exercise HDFSRecordStorage behaviors and SequentialRecordStorage.getStorage dispatch for hdfs:// / HDFS://.
  • Updated SequentialRecordStorageSpec documentation comment to point hdfs:// coverage at the new spec (instead of claiming it is untestable / indirectly covered elsewhere).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
amber/src/test/scala/org/apache/texera/amber/engine/common/storage/SequentialRecordStorageSpec.scala Updates the factory-dispatch commentary to reflect that hdfs:// coverage lives in HDFSRecordStorageSpec.
amber/src/test/scala/org/apache/texera/amber/engine/common/storage/HDFSRecordStorageSpec.scala New spec covering constructor scheme guard, auto-create-folder behavior, writer/reader round-trips, missing-file fallback, delete/idempotency, containsFolder semantics, and factory dispatch for hdfs:// / HDFS://.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@aglinxinyuan
aglinxinyuan added this pull request to the merge queue Jul 30, 2026
Merged via the queue into apache:main with commit 4e152e0 Jul 30, 2026
24 checks passed
@aglinxinyuan
aglinxinyuan deleted the test-hdfs-record-storage branch July 30, 2026 03:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add unit tests for HDFSRecordStorage and the hdfs record-storage dispatch

4 participants