Skip to content

fix(spark): return an unqualified basePath from SparkClientFunctionalTestHarness - #19747

Open
deepakpanda93 wants to merge 2 commits into
apache:masterfrom
deepakpanda93:fix/HUDI-6042-harness-basepath-scheme
Open

fix(spark): return an unqualified basePath from SparkClientFunctionalTestHarness#19747
deepakpanda93 wants to merge 2 commits into
apache:masterfrom
deepakpanda93:fix/HUDI-6042-harness-basepath-scheme

Conversation

@deepakpanda93

@deepakpanda93 deepakpanda93 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Describe the issue this Pull Request addresses

Closes #15888 (HUDI-6042).

SparkClientFunctionalTestHarness#basePath() returns a scheme-qualified path, unlike HoodieCommonTestHarness, CLIFunctionalTestHarness and the examples harness, which all return an unqualified one.

The scheme breaks any helper that passes the value to java.nio.file.Paths:

Paths.get("file:///var/.../dataset", "2016/03/15")  ->  file:/var/.../dataset/2016/03/15
isAbsolute = false

file: is read as an ordinary directory name, so the result is a relative path. FileCreateUtils#createPartitionMetaFile (line 398) and createMarkerFile (line 448) hand the raw string to Paths.get, so they wrote under the working directory instead of the table, without failing. Lines 384 and 409 of the same class normalise via getBasePath().toUri().getPath() and were unaffected, which is why this stayed hidden.

TestSparkSampleWritesUtils carried a basePath() override with a TODO remove this and fix parent class (HUDI-6042) comment, working around exactly this.

Summary and Changelog

basePath() now returns the unqualified path; a new baseUri() returns the URI form for callers that want the scheme. This aligns the harness with the three other test harnesses in the repo.

  • SparkClientFunctionalTestHarness: basePath() returns tempDir.toAbsolutePath().toString(); added baseUri().
  • Removed 20 workarounds that existed only because of this bug:
    • URI.create(basePath()).getPath() x18 in TestRollingMetadata, TestSparkRDDWriteClient, TestUpgradeDowngrade
    • basePath().substring(7) in TestHoodieSparkRollback
    • the HUDI-6042 override in TestSparkSampleWritesUtils
  • Updated call sites that consumed the scheme implicitly:
    • TestSparkRDDWriteClient: basePath + "_UTC" -> "/_UTC". toUri() appends a trailing slash for an existing directory and toString() does not, so this had become a sibling directory escaping @TempDir cleanup.
    • TestHoodieSparkRollback (x2): basePath + ".hoodie/" -> "/.hoodie/", same trailing-slash reason.
    • TestHoodieSparkRollback (x3): metaClient.getBasePath().toString().substring(5) -> getBasePath().toUri().getPath(). The old form stripped a file: prefix that no longer exists; the new form yields the path component whether or not a scheme is present, matching the idiom already used in FileCreateUtils.
  • Added TestSparkClientFunctionalTestHarness covering the path contract.

Impact

Test infrastructure only. No production code is touched. Callers that need the scheme use baseUri().

Risk Level

low

Aligns this harness with HoodieCommonTestHarness, CLIFunctionalTestHarness and the examples harness, all of which already return unqualified paths. The change is toward the more permissive form: Hadoop and StoragePath accept both qualified and unqualified paths, while java.nio.file.Paths accepts only unqualified.

Verification:

Suite Result
TestSparkClientFunctionalTestHarness (new) 4/4
TestSparkRDDWriteClient 19/19
TestRollingMetadata 10/10
TestUpgradeDowngrade 52/52
TestSparkSampleWritesUtils 3/3, override deleted
TestHoodieSparkCopyOnWriteTableRollbackTableVersionSix 4/4
TestHoodieSparkMergeOnReadTableRollback 26/26

Negative control: reverting only the harness body while keeping the new tests fails 3 of the 4, including the end-to-end case asserting partition metadata lands under the table directory. The fourth (storage resolution) passes either way and is a regression guard rather than a bug detector.

The two rollback classes were also run against an all-master baseline (source and installed artifacts both at master) and pass 30/30 on both sides, confirming no regression.

Since the compiler cannot catch a change of String content, all 77 direct and transitive subclasses of the harness were swept for every syntactic way a caller could depend on the scheme: substring on a base path, replace/startsWith/contains("file:"), split/indexOf on a colon, URI/URL construction, length() arithmetic, regex, and concatenation without a separator. None remain. TestGcsEventsHoodieIncrSource overrides basePath() to return the qualified form (pre-existing, from HUDI-4850) and is therefore unaffected.

That sweep cannot see indirect dependencies, where no "file:" literal appears and a basePath()-derived path is instead compared against one the filesystem has qualified. CI found exactly one such case, TestHoodieSparkMergeOnReadTableCompaction#validateFileListingInMetadataTable, now fixed by building its partition paths from baseUri(). The remaining new StoragePath(basePath(), ...) sites in that class feed exists(), listFiles() and deleteFile(), which do not compare path strings.

Documentation Update

none

Contributor's checklist

  • Read through contributor's guide
  • Change Logs and Impact were stated clearly
  • Adequate tests were added if applicable
  • CI passed

…TestHarness

SparkClientFunctionalTestHarness#basePath() returned a scheme-qualified path,
unlike HoodieCommonTestHarness, CLIFunctionalTestHarness and the examples
harness, which all return an unqualified one.

The scheme breaks any helper that passes the value to java.nio.file.Paths.
Paths.get("file:///a/b", "c") yields the relative path "file:/a/b/c", with
"file:" read as an ordinary directory name, so FileCreateUtils wrote partition
data under the working directory instead of the table, without failing.

basePath() now returns the unqualified path and baseUri() exposes the URI for
callers that want the scheme. This removes 20 workarounds, including the
basePath() override in TestSparkSampleWritesUtils that cited this ticket and
basePath().substring(7) in TestHoodieSparkRollback.

Call sites that consumed the scheme implicitly are updated too: string
concatenation that relied on the trailing slash toUri() appends for an existing
directory, and substring(5) on metaClient.getBasePath() that stripped the
"file:" prefix. The latter now uses getBasePath().toUri().getPath(), which
yields the path component whether or not a scheme is present.

TestSparkClientFunctionalTestHarness covers the new contract.
@github-actions github-actions Bot added the size:M PR with lines of changes in (100, 300] label Aug 26, 2026

@hudi-agent hudi-agent 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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR makes SparkClientFunctionalTestHarness#basePath() return an unqualified path (adding baseUri() for scheme-needing callers), aligning it with the other test harnesses and removing ~20 workarounds that existed only because of the scheme leaking into java.nio.file.Paths. I traced the updated call sites — the string-concatenation spots consistently gained the leading / now that the returned path has no trailing slash, the getHoodieMetaClient calls receive the same value the old URI.create(...).getPath() produced, and the _UTC path now stays under @TempDir. The broad blast radius (many untouched tests inherit basePath()) is best validated by CI, which exercises those paths. No issues flagged from this automated pass — a Hudi committer or PMC member can take it from here for a final review.

cc @yihua

@codecov-commenter

codecov-commenter commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.97%. Comparing base (ef07f0f) to head (c4bf568).
⚠️ Report is 10 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19747      +/-   ##
============================================
+ Coverage     77.95%   77.97%   +0.02%     
- Complexity    33447    33461      +14     
============================================
  Files          2539     2539              
  Lines        140891   140939      +48     
  Branches      17008    17036      +28     
============================================
+ Hits         109829   109898      +69     
+ Misses        23401    23383      -18     
+ Partials       7661     7658       -3     
Components Coverage Δ
hudi-common 83.49% <ø> (+0.04%) ⬆️
hudi-client 83.07% <ø> (+0.04%) ⬆️
hudi-flink 85.62% <ø> (-0.05%) ⬇️
hudi-spark-datasource 72.37% <ø> (+<0.01%) ⬆️
hudi-utilities 74.34% <ø> (+0.01%) ⬆️
hudi-cli 15.06% <ø> (ø)
hudi-hadoop 69.23% <ø> (ø)
hudi-sync 75.56% <ø> (+0.11%) ⬆️
hudi-io 79.85% <ø> (ø)
hudi-timeline-service 83.83% <ø> (+0.39%) ⬆️
hudi-cloud 64.27% <ø> (-0.06%) ⬇️
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 51.03% <ø> (+0.09%) ⬆️
flink-integration-tests 49.00% <ø> (-0.01%) ⬇️
hadoop-mr-java-client 43.74% <ø> (+0.01%) ⬆️
integration-tests 13.56% <ø> (-0.01%) ⬇️
spark-client-hadoop-common 50.54% <ø> (-0.01%) ⬇️
spark-java-tests 52.02% <ø> (+0.02%) ⬆️
spark-scala-tests 46.64% <ø> (+0.01%) ⬆️
utilities 36.42% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 28 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@deepakpanda93 deepakpanda93 changed the title [HUDI-6042] Return an unqualified basePath from SparkClientFunctionalTestHarness fix(spark): return an unqualified basePath from SparkClientFunctionalTestHarness Aug 26, 2026
validateFileListingInMetadataTable builds partition paths from basePath() and
compares the resulting listing against a direct storage listing. The filesystem
qualifies the paths it returns, so the two sides only match when the partition
paths carry the scheme too. basePath() supplied it before this branch made the
harness return an unqualified path.

Build the partition paths from baseUri() instead, which is what that accessor
is for. This restores the exact strings the comparison saw previously and keeps
the assertion strict rather than making it scheme-insensitive.

Caught by test-spark-java17-java-tests-part2 (scala-2.13, spark4.2) on
TestHoodieSparkMergeOnReadTableCompaction.

@hudi-agent hudi-agent 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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR makes SparkClientFunctionalTestHarness#basePath() return an unqualified path (adding baseUri() for the scheme form), aligning it with the other test harnesses and removing the Paths.get-driven relative-path workarounds. I traced the string-concatenation call sites (trailing-slash handling in the rollback and _UTC cases), the deliberate baseUri() retention in the MDT listing comparison, and confirmed via a repo-wide grep that all URI.create(basePath())/substring workarounds live only in the edited files. No correctness issues found. A few style/readability suggestions in the inline comments. Please take a look, and this should be ready for a Hudi committer or PMC member to take it from here. Code is clean overall; one minor readability nit on an inline fully-qualified type.

cc @yihua

"partition path must sit under the table directory, but was " + partition);
}

@Test

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.

🤖 nit: since there's no competing Path type imported here, could you add import java.nio.file.Path; and drop the fully-qualified name for readability?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

@hudi-bot

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands @hudi-bot supports the following commands:
  • @hudi-bot run azure re-run the last Azure build

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M PR with lines of changes in (100, 300]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix SparkClientFunctionalTestHarness basePath

4 participants