Skip to content

Add automatic volume creation for local input source #4961

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

Olg-K
Copy link
Contributor

@Olg-K Olg-K commented Apr 23, 2025

Local Storage Provider Enhancements

  • localDirectory input source renamed to local. The old name remains valid and supports all new functionality
  • Local storage provider now creates empty files/directories when source paths don't exist, instead of failing
  • Added new CreateAs property to Local InputSource Params to control file/directory creation behavior

Background

Currently, when a local input source (file or directory) specified in a Job spec doesn't exist on a compute node, the node will not bid for execution. This change allows compute nodes to bid if they can create an empty directory or file at the specified path. When such a bid is accepted, the compute node will create the empty file/directory and mount it normally.

CreateAs Parameter

A new optional CreateAs property can now be used in InputSource Params with the following values:

  • infer (default): The driver will attempt to determine whether to create a file or directory based on the source path
  • dir: The driver will create an empty directory
  • file: The driver will create an empty file
  • nocreate: The driver will not attempt to create anything

Both files and directories are created with -rwx------@ permissions.
Important: To enable automatic creation, ReadWrite must be set to true.

Example:

InputSources:
  - Source:
      Type: "local"
      Params:
        SourcePath: "/tmp/empty_file"
        ReadWrite: true
        CreateAs: "file"
    Target: "/config_file"

Volume Type Inference

The inference is currently based on source path characteristics. The driver determines if a path represents a file or directory based on common conventions - for example, paths with trailing slashes are treated as directories. This is a best-effort approach; for precise control, users should explicitly set the CreateAs property to either "dir" or "file".

Addresses #3922

Summary by CodeRabbit

  • New Features

    • Introduced configurable strategies for creating local storage paths, allowing explicit control over whether a path is created as a file, directory, or inferred automatically.
    • Improved error handling and validation for local storage creation strategies.
  • Bug Fixes

    • Enhanced compatibility and support for legacy local storage naming conventions.
  • Refactor

    • Unified and modernized the local storage implementation, consolidating previous local directory logic.
    • Deprecated the old local directory storage type in favor of a new, streamlined local storage type.
    • Updated storage source naming to use a new local storage type with deprecation of the old local directory type.
  • Tests

    • Added comprehensive unit tests for local storage creation strategies and provider logic.
    • Removed outdated tests for the deprecated local directory storage provider.

- Change the spec value for local provider from 'localDirectory' to 'local'
- Introduce CreateAs param to handle non-existent volumes
- Support creation of both files and directories based on source path
- Add inference logic to determine appropriate creation strategy
- Improve error messaging

Currently if a local input source (file or directory) specified in the Job spec does not exist on a compute node, it will not try to bid for the execution. This changes the behavior to allow compute nodes to bid if they can create an empty directory or file at the specified path. When such a bid is accepted, the compute node will create an empty file or directory and mount it as normal. This change also introduces a new parameter to the Job spec input source 'CreateAs', which allows the user to specify how they want the input source to be created. By default the system will try to infer the volume type (file/directory) based on the provided path.
Copy link

linear bot commented Apr 23, 2025

@Olg-K Olg-K requested a review from wdbaruni April 23, 2025 05:09
Copy link
Contributor

coderabbitai bot commented Apr 23, 2025

Walkthrough

The changes refactor and enhance the local storage subsystem by consolidating the previous localDirectory storage under a new local package and storage source type. A new, flexible "create strategy" mechanism is introduced, allowing explicit or inferred creation of files or directories when preparing storage, particularly for read-write scenarios. The codebase updates all references and tests to use the new local storage type, marks the old constant as deprecated, and adds comprehensive tests for the new logic. The legacy localDirectory storage is fully replaced, and its tests are removed.

Changes

File(s) Change Summary
pkg/models/constants.go Added new constant StorageSourceLocal, marked StorageSourceLocalDirectory as deprecated, updated storage names.
pkg/storage/local/create_strategy.go
pkg/storage/local/create_strategy_test.go
Introduced new create strategy type, constants, parsing, inference, and unit tests for handling file/directory creation strategies.
pkg/storage/local/storage.go Refactored storage provider logic: supports creating files/directories if missing, added creation strategy handling, and improved error reporting.
pkg/storage/local/types.go Updated to accept new/legacy storage types, added CreateAs field to source, improved decoding logic.
pkg/storage/local/storage_test.go Added comprehensive tests for the new local storage provider, including creation, legacy support, and error handling.
pkg/storage/local_directory/storage_test.go Deleted legacy local directory storage tests.
pkg/executor/util/utils.go
pkg/test/compute/utils_test.go
pkg/test/devstack/jobselection_test.go
pkg/test/scenario/storage.go
cmd/util/opts/storage_specconfig.go
Updated imports and references from local_directory to local, updated usage to new storage source type.
pkg/bidstrategy/semantic/export_test.go
pkg/bidstrategy/semantic/input_locality_test.go
pkg/bidstrategy/semantic/storage_installed_test.go
Updated tests to use StorageSourceLocal instead of StorageSourceLocalDirectory in input specs.
pkg/storage/noop/noop.go Updated noop storage to use new storage source type in returned spec configs.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant StorageProvider
    participant Filesystem

    Client->>StorageProvider: PrepareStorage(Source, ReadWrite, CreateAs)
    alt Volume exists
        StorageProvider->>Filesystem: Check existence
        StorageProvider-->>Client: Return volume
    else Volume does not exist & ReadWrite true
        StorageProvider->>Filesystem: Create volume (dir/file/infer)
        Filesystem-->>StorageProvider: Success/Error
        StorageProvider-->>Client: Return volume or error
    else Volume does not exist & ReadOnly
        StorageProvider-->>Client: Return error (cannot create)
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Allow localDirectory storage to create directories/files if they don't exist and if ReadWrite is true (ENG-42)

Poem

A bunny hops through code anew,
Local storage gets a clever view!
Now files and dirs appear on cue,
When ReadWrite’s set, creation’s true.
Old paths retire, new ones shine bright—
Test suites leap with pure delight!
🐇✨


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
pkg/storage/local/types.go (1)

45-57: Improved error handling and CreateStrategy conversion.

The code now properly declares the error variable upfront and returns immediately on decode failure. The additional logic to convert the string representation of CreateAs to a CreateStrategy type is well-implemented, with appropriate error handling.

Consider adding a comment above line 54 to clarify why the conversion from string to CreateStrategy is necessary, such as:

  // here c.CreateAs is a string, try to convert it to CreateStrategy
  c.CreateAs, err = CreateStrategyFromString(c.CreateAs.String())
pkg/storage/local/storage.go (1)

178-183: Use .String() to future‑proof hint message

The hint embeds the Dir/File constants directly. Relying on the implicit %s string conversion of a custom type works today but will break if the underlying representation changes (e.g. to an enum). Safer to call .String() explicitly.

-			WithHint(fmt.Sprintf("If you want the job to create the volume, set the CreateAs property to either '%s' or '%s'", Dir, File))
+			WithHint(fmt.Sprintf("If you want the job to create the volume, set the CreateAs property to either '%s' or '%s'", Dir.String(), File.String()))
pkg/storage/local/create_strategy.go (1)

65-67: Spelling: “Additionally”

Minor typo in comment – improves readability.

-	// Additinally, we can look at Target value to see if it gives more insight on whether we should create a file or directory.
+	// Additionally, we can look at the Target value to see if it gives more insight on whether we should create a file or directory.
pkg/storage/local/storage_test.go (1)

520-531: Propagate errors from CreateStrategyFromString in helper

The helper ignores the error, which would hide test failures for invalid createAs values. Failing fast makes the suite safer.

-	createStrategy, _ := CreateStrategyFromString(createAs)
+	createStrategy, err := CreateStrategyFromString(createAs)
+	require.NoError(s.T(), err)

(Import require or bubble the error.)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d12d76 and cb10e8f.

📒 Files selected for processing (16)
  • cmd/util/opts/storage_specconfig.go (1 hunks)
  • pkg/bidstrategy/semantic/export_test.go (1 hunks)
  • pkg/bidstrategy/semantic/input_locality_test.go (1 hunks)
  • pkg/bidstrategy/semantic/storage_installed_test.go (1 hunks)
  • pkg/executor/util/utils.go (2 hunks)
  • pkg/models/constants.go (1 hunks)
  • pkg/storage/local/create_strategy.go (1 hunks)
  • pkg/storage/local/create_strategy_test.go (1 hunks)
  • pkg/storage/local/storage.go (7 hunks)
  • pkg/storage/local/storage_test.go (1 hunks)
  • pkg/storage/local/types.go (4 hunks)
  • pkg/storage/local_directory/storage_test.go (0 hunks)
  • pkg/storage/noop/noop.go (1 hunks)
  • pkg/test/compute/utils_test.go (1 hunks)
  • pkg/test/devstack/jobselection_test.go (1 hunks)
  • pkg/test/scenario/storage.go (1 hunks)
💤 Files with no reviewable changes (1)
  • pkg/storage/local_directory/storage_test.go
🧰 Additional context used
🧬 Code Graph Analysis (8)
pkg/test/compute/utils_test.go (4)
pkg/models/input_source.go (1)
  • InputSource (13-23)
pkg/storage/local/types.go (1)
  • Source (15-19)
pkg/models/spec_config.go (1)
  • SpecConfig (14-20)
pkg/models/constants.go (1)
  • StorageSourceLocal (61-61)
pkg/bidstrategy/semantic/export_test.go (2)
pkg/storage/local/types.go (2)
  • Source (15-19)
  • NewSpecConfig (62-72)
pkg/models/constants.go (1)
  • StorageSourceLocal (61-61)
pkg/executor/util/utils.go (3)
pkg/models/constants.go (2)
  • StorageSourceLocal (61-61)
  • StorageSourceLocalDirectory (60-60)
pkg/storage/local/storage.go (2)
  • NewStorageProvider (26-33)
  • StorageProviderParams (19-21)
pkg/storage/local/types.go (1)
  • ParseAllowPaths (107-113)
pkg/bidstrategy/semantic/storage_installed_test.go (2)
pkg/storage/local/types.go (2)
  • Source (15-19)
  • NewSpecConfig (62-72)
pkg/models/constants.go (1)
  • StorageSourceLocal (61-61)
pkg/bidstrategy/semantic/input_locality_test.go (2)
pkg/storage/local/types.go (1)
  • Source (15-19)
pkg/models/constants.go (1)
  • StorageSourceLocal (61-61)
pkg/storage/noop/noop.go (1)
pkg/models/constants.go (1)
  • StorageSourceLocal (61-61)
pkg/storage/local/types.go (3)
pkg/storage/local/create_strategy.go (2)
  • CreateStrategy (11-11)
  • CreateStrategyFromString (39-57)
pkg/models/spec_config.go (1)
  • SpecConfig (14-20)
pkg/models/constants.go (2)
  • StorageSourceLocal (61-61)
  • StorageSourceLocalDirectory (60-60)
pkg/storage/local/create_strategy.go (2)
pkg/bacerrors/error.go (1)
  • Newf (105-107)
pkg/bacerrors/codes.go (1)
  • ValidationError (17-17)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Container Tests / Run
  • GitHub Check: Integration Tests / Run
  • GitHub Check: Unit Tests / Run
🔇 Additional comments (21)
cmd/util/opts/storage_specconfig.go (1)

15-15: Import path updated as part of package renaming.

The import path has been updated to reflect the renaming of the local_directory package to local, which aligns with the PR's objective of renaming the storage source type while maintaining backward compatibility.

pkg/test/devstack/jobselection_test.go (1)

18-18: Import path updated for consistency with package renaming.

The import path has been updated from local_directory to local to match the package renaming, while maintaining the same alias (storage_local). This ensures that all test code using this import continues to function properly.

pkg/test/scenario/storage.go (1)

17-17: Import path updated to use the renamed local storage package.

The import path has been updated from local_directory to local to align with the package renaming. All function calls to storage_local.NewSpecConfig() throughout this file will now use the implementation from the renamed package.

pkg/bidstrategy/semantic/input_locality_test.go (1)

31-31: Storage source type updated from StorageSourceLocalDirectory to StorageSourceLocal.

The test now uses the new StorageSourceLocal constant instead of the deprecated StorageSourceLocalDirectory, which aligns with the PR's objective of renaming the storage source type. This change ensures consistent use of the new storage type across the codebase.

pkg/bidstrategy/semantic/storage_installed_test.go (1)

25-25: Correct update to use the new storage source type.

The change from StorageSourceLocalDirectory to StorageSourceLocal is consistent with the PR objectives to rename the local storage provider. This ensures the test uses the new standardized constant for local storage.

pkg/storage/noop/noop.go (1)

102-102: Properly updated storage source type in noop implementation.

The Upload method now correctly returns a SpecConfig with the updated StorageSourceLocal type, which aligns with the source type standardization across the codebase.

pkg/test/compute/utils_test.go (2)

11-11: Correctly updated import path for local storage package.

The import path has been properly updated to point to the new package location with an appropriate alias.


17-19: Updated storage type and parameter struct correctly.

The code now uses models.StorageSourceLocal and the corresponding storage_local.Source struct, which aligns with the package renaming and maintains functionality.

pkg/bidstrategy/semantic/export_test.go (1)

24-24: Properly updated storage source type in test helper.

The test helper function now correctly uses models.StorageSourceLocal instead of the deprecated StorageSourceLocalDirectory, ensuring consistency across the codebase.

pkg/executor/util/utils.go (3)

19-19: Appropriate use of import alias for the local storage package.

The change from localdirectory to local_storage import alias accurately reflects the package renaming while maintaining a descriptive alias that clearly indicates its purpose.


65-69: Use of IsNotDisabled with the new storage source type is correct.

The code correctly updates the check to use models.StorageSourceLocal instead of the deprecated models.StorageSourceLocalDirectory, aligning with the package renaming strategy.


74-76: Well-implemented backward compatibility approach.

Excellent approach to maintain backward compatibility by registering the same storage provider instance under both the new models.StorageSourceLocal and deprecated models.StorageSourceLocalDirectory names. This ensures existing code continues to work while facilitating migration to the new name.

pkg/models/constants.go (2)

60-61: Good use of deprecation comment and consistent naming.

The code properly marks StorageSourceLocalDirectory as deprecated with a clear comment indicating to use StorageSourceLocal instead. The new constant follows the same naming convention and has a cleaner, more concise value.


68-68: Correctly added new source type to StoragesNames slice.

Adding StorageSourceLocal to the StoragesNames slice ensures it's recognized throughout the system while maintaining backward compatibility by keeping the deprecated StorageSourceLocalDirectory.

pkg/storage/local/create_strategy_test.go (3)

10-15: Comprehensive test for allowed create strategies.

The test correctly verifies that all valid strategies are included in the AllowedCreateStrategies function result, providing good coverage for this fundamental aspect of the new functionality.


17-77: Well-structured test for CreateStrategyFromString.

This test thoroughly covers all valid inputs, including empty string defaulting, as well as error handling for invalid inputs. The table-driven test approach provides clear test cases and expectations.


79-128: Comprehensive test for path-based strategy inference.

The test covers a wide range of path patterns including absolute/relative paths, with/without trailing slashes, with/without extensions, and empty paths. Each case correctly validates the expected strategy inference logic.

pkg/storage/local/types.go (4)

1-1: Package renamed correctly.

The package has been appropriately renamed from localdirectory to local as part of the broader refactoring effort.


18-18: Good addition of CreateStrategy field.

The CreateAs field of type CreateStrategy has been properly added to the Source struct, enabling the new functionality for specifying how local storage volumes should be created.


26-34: Well-implemented storage source type compatibility.

The code now correctly accepts both models.StorageSourceLocal and models.StorageSourceLocalDirectory in the DecodeSpec function, supporting backward compatibility while encouraging use of the new type. The error message has been improved for clarity using fmt.Sprintf.


69-69: Updated return type for NewSpecConfig.

The function now correctly returns a SpecConfig with Type set to models.StorageSourceLocal instead of the deprecated models.StorageSourceLocalDirectory.

- Some renamings
- Spellcheck fixes
- Better handling of nocreate strategy
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
pkg/storage/local/create_strategy.go (1)

47-65: Parse is strict‑case and whitespace‑sensitive – consider being more forgiving

CreateStrategyFromString currently compares the raw string literal against the four canonical spellings.
A single accidental capital letter ("Dir") or a trailing space ("dir "), both fairly common in YAML/JSON, will trigger a validation error even though the intent is unambiguous.

-func CreateStrategyFromString(s string) (CreateStrategy, error) {
-	switch s {
+func CreateStrategyFromString(raw string) (CreateStrategy, error) {
+	s := strings.TrimSpace(strings.ToLower(raw))
 	switch s {

The change trims leading/trailing whitespace and normalises the word to lower‑case before the switch, improving UX without sacrificing type‑safety.
(Add a quick unit‑test in create_strategy_test.go to lock the behaviour in.)

pkg/storage/local/storage.go (2)

31-33: Wrong format verb prints garbled slice

log.Debug().Msgf("Local driver created with allowedPaths: %s", storageHandler.allowedPaths)
uses %s, but storageHandler.allowedPaths is a slice of structs, not a string.
At runtime this produces something like [%!s(MISSING)], hurting debuggability.

- log.Debug().Msgf("Local driver created with allowedPaths: %s", storageHandler.allowedPaths)
+ log.Debug().Msgf("Local driver created with allowedPaths: %+v", storageHandler.allowedPaths)

68-81: Duplicate “may‑create” logic – extract helper?

HasStorageLocally and GetVolumeSize now contain identical checks:

source.ReadWrite && source.CreateAs != NoCreate

Centralising this in a small func (s Source) creationAllowed() bool { … } would:

  • prevent future drift (e.g. if a new permissive strategy is added),
  • clarify intent at the call‑site.

Not critical, but would improve maintainability.

pkg/storage/local/storage_test.go (1)

119-123: Tiny typo in comment

"file doesn't exist and does not allow read-write permission, dirver does not allow read-write permission"driver.

Purely cosmetic – ignore if the suite is about to be re‑worked.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between cb10e8f and d88dbfa.

📒 Files selected for processing (4)
  • pkg/storage/local/create_strategy.go (1 hunks)
  • pkg/storage/local/create_strategy_test.go (1 hunks)
  • pkg/storage/local/storage.go (7 hunks)
  • pkg/storage/local/storage_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/storage/local/create_strategy_test.go
🧰 Additional context used
🧠 Learnings (1)
pkg/storage/local/storage.go (2)
Learnt from: Olg-K
PR: bacalhau-project/bacalhau#4961
File: pkg/storage/local/storage.go:0-0
Timestamp: 2025-04-23T06:18:01.350Z
Learning: In the Bacalhau project's storage package, when creating empty files, it's preferable to use `os.OpenFile()` with `O_CREATE|O_EXCL|O_RDONLY` flags rather than `os.Create()` to avoid race conditions and potential data loss in multi-process environments.
Learnt from: Olg-K
PR: bacalhau-project/bacalhau#4961
File: pkg/storage/local/storage.go:0-0
Timestamp: 2025-04-23T06:18:01.350Z
Learning: In the Bacalhau project's storage package, when creating empty files, it's preferable to use `os.OpenFile()` with `O_CREATE|O_EXCL|O_RDONLY` flags rather than `os.Create()` to avoid race conditions and potential data loss in multi-process environments.
🧬 Code Graph Analysis (1)
pkg/storage/local/create_strategy.go (2)
pkg/bacerrors/error.go (1)
  • Newf (105-107)
pkg/bacerrors/codes.go (1)
  • ValidationError (17-17)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Coverage / Report
  • GitHub Check: Container Tests / Run
🔇 Additional comments (3)
pkg/storage/local/create_strategy.go (1)

70-85: Inference logic silently assumes POSIX paths

InferCreateStrategyFromPath relies on / semantics and filepath.Split heuristics.
On Windows a path like C:\data\ is still recognised (back‑slash is handled), but C:\data (no trailing slash) will be treated as a file, which is probably not what the user meant.
The docstring hints at the limitation for dotted directories, but you may also want to:

  • mention the Windows caveat in the comment, and/or
  • add a short platform‑specific test verifying the current behaviour so that future refactors don’t introduce surprises.

No code change required – this is just a heads‑up.

pkg/storage/local/storage.go (2)

50-56: 👍 Creation‑permission check now guards bidding

The additional source.CreateAs != NoCreate clause closes the loophole previously flagged where a node would bid on a job even though creation was explicitly disabled.
Looks good.


219-224: File created with O_RDONLY – double‑check on Windows

os.OpenFile(filePath, os.O_CREATE|os.O_EXCL|os.O_RDONLY, …) works fine on Linux, but on Windows O_RDONLY forbids deleting an open handle, which can trip later clean‑up in tests.
Opening with os.O_WRONLY (we never read the file) avoids that quirk while still preventing accidental truncation.

Up to you – just flagging the cross‑platform nuance.

@Olg-K Olg-K mentioned this pull request Apr 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

1 participant