Skip to content

✅ Add comprehensive tests for extract owner options#2596

Open
ChanTsune wants to merge 2 commits intomainfrom
cli/extract/expand-test-case
Open

✅ Add comprehensive tests for extract owner options#2596
ChanTsune wants to merge 2 commits intomainfrom
cli/extract/expand-test-case

Conversation

@ChanTsune
Copy link
Copy Markdown
Owner

@ChanTsune ChanTsune commented Jan 11, 2026

Summary by CodeRabbit

  • Tests
    • Added comprehensive Unix-only tests for CLI ownership extraction covering owner flags, numeric behavior, and precedence.
    • Registered the new test module so it runs with the existing suite.
    • Added a Unix test helper and a skip-if-not-root test macro to safely skip ownership tests when not running as root.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 11, 2026

📝 Walkthrough

Walkthrough

Adds Unix-only test utilities and a new ownership-focused extract test module, plus a minor test skip refactor and a module export gated to Unix targets. All changes are test-only; no public API behavior altered.

Changes

Cohort / File(s) Summary
Test module registration
cli/tests/cli/extract.rs
Added mod option_owner; to load the new ownership tests.
New ownership tests
cli/tests/cli/extract/option_owner.rs
New comprehensive Unix-focused test module (owner restoration, --same-owner/--no-same-owner, --uid/--gid/--uname/--gname, --numeric-owner, precedence rules, root vs non-root guarded tests, helpers for archive creation).
Skip/refactor in an existing test
cli/tests/cli/extract/option_chroot.rs
Replaced manual root-check with skip_if_not_root import and macro usage to skip tests when not root. Minor lines adjusted.
Unix test utilities export
cli/tests/cli/utils.rs
Added #[cfg(unix)] pub mod unix; to expose Unix-only helpers conditionally.
New Unix helper macro
cli/tests/cli/utils/unix.rs
Added #[macro_export] skip_if_not_root macro and pub use skip_if_not_root; to allow tests to skip when not run as root.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 In burrows deep I code and hop,
New tests ensure ownership won't flop,
UIDs, GIDs, names in a gentle spin,
Root-aware checks keep mistakes from in,
A carrot for CI — hop, merge, and grin! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding comprehensive tests for extract owner options. This is directly reflected in the new test module for ownership-related options.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @ChanTsune, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the test coverage for the extract command within the pna CLI tool. It introduces a dedicated suite of tests to validate the correct behavior of all ownership-related options, such as preserving, overriding, or ignoring user and group information during archive extraction on Unix-like systems. This ensures the robustness and reliability of file ownership handling, particularly in environments where permissions are critical.

Highlights

  • New Test Module: A new test module, option_owner.rs, has been introduced to specifically cover ownership-related functionalities of the extract command.
  • Comprehensive Ownership Option Testing: Extensive tests have been added for various extract command options, including --same-owner, --no-same-owner, --uname, --gname, --uid, --gid, and --numeric-owner, ensuring correct handling of file ownership during extraction.
  • Root Privilege Handling: Tests that require root privileges to modify file ownership are now conditionally executed using a skip_if_not_root! macro, improving test suite reliability across different user environments.
  • Option Precedence and Default Behavior: Specific test cases verify the precedence of ownership options (e.g., --uid over --uname) and confirm the default ownership behavior when no explicit --same-owner or --no-same-owner flag is provided.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added the cli This issue is about cli application label Jan 11, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds a comprehensive suite of tests for the owner-related options of the extract command. The new tests are well-structured, cover a wide range of scenarios including various flags and their interactions, and handle environment-specific conditions like root privileges gracefully. The changes are a great addition to ensure the correctness of file ownership handling. I have a few suggestions to improve maintainability and strengthen some assertions.

Comment thread cli/tests/cli/extract/option_owner.rs
Comment thread cli/tests/cli/extract/option_owner.rs
Comment on lines +350 to +354
assert_ne!(
meta.uid(),
1000,
"extracted file should not have original uid after --uname override"
);
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.

medium

The assertion assert_ne!(meta.uid(), 1000, ...) is correct but could be stronger. Since the test is skipped if the 'nobody' user doesn't exist, you can assert that the extracted file's UID is exactly the UID of 'nobody' when the test runs.

    let nobody_user = nix::unistd::User::from_name("nobody")
        .expect("user 'nobody' lookup failed")
        .expect("'nobody' user not found, but extraction succeeded");
    assert_eq!(
        meta.uid(),
        nobody_user.uid.as_raw(),
        "extracted file should be owned by 'nobody'"
    );

Comment on lines +412 to +416
assert_ne!(
meta.gid(),
1000,
"extracted file should not have original gid after --gname override"
);
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.

medium

Similar to the uname override test, this assertion can be strengthened. Since the test skips if 'nogroup' doesn't exist, you can assert that the file's GID matches the GID of 'nogroup' for a more precise check.

    let nogroup_group = nix::unistd::Group::from_name("nogroup")
        .expect("group 'nogroup' lookup failed")
        .expect("'nogroup' group not found, but extraction succeeded");
    assert_eq!(
        meta.gid(),
        nogroup_group.gid.as_raw(),
        "extracted file should have GID of 'nogroup'"
    );

let current_uid = nix::unistd::Uid::effective().as_raw();
let current_gid = nix::unistd::Gid::effective().as_raw();

assert_eq!(

Check failure

Code scanning / CodeQL

Cleartext logging of sensitive information High test

This operation writes
archive_uid
to a log file.

Copilot Autofix

AI 4 months ago

In general, to fix cleartext logging issues, avoid including sensitive data directly in log or assertion messages; instead, use generic messages or, if absolutely needed, sanitize/obfuscate or encrypt the data before logging. Here, the only use of archive_uid and archive_gid in a logging/formatting context is within the assert_eq! failure messages. The numeric values are not structurally required for the test—they only serve to make error messages more descriptive.

The best fix that preserves behavior is to keep the assertions but change their custom failure strings so they no longer format archive_uid and archive_gid. We can keep the descriptive text while dropping the {} placeholders and the corresponding arguments, e.g. change "..., not archive's uid {}" to "..., not archive's uid". No new imports or helper functions are needed; we only adjust the assert_eq! invocations in cli/tests/cli/extract/option_owner.rs at lines 106–111 and 112–117.

Suggested changeset 1
cli/tests/cli/extract/option_owner.rs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/cli/tests/cli/extract/option_owner.rs b/cli/tests/cli/extract/option_owner.rs
--- a/cli/tests/cli/extract/option_owner.rs
+++ b/cli/tests/cli/extract/option_owner.rs
@@ -106,14 +106,12 @@
     assert_eq!(
         meta.uid(),
         current_uid,
-        "extracted file should be owned by current user, not archive's uid {}",
-        archive_uid
+        "extracted file should be owned by current user, not archive's uid",
     );
     assert_eq!(
         meta.gid(),
         current_gid,
-        "extracted file should be owned by current group, not archive's gid {}",
-        archive_gid
+        "extracted file should be owned by current group, not archive's gid",
     );
 }
 
EOF
@@ -106,14 +106,12 @@
assert_eq!(
meta.uid(),
current_uid,
"extracted file should be owned by current user, not archive's uid {}",
archive_uid
"extracted file should be owned by current user, not archive's uid",
);
assert_eq!(
meta.gid(),
current_gid,
"extracted file should be owned by current group, not archive's gid {}",
archive_gid
"extracted file should be owned by current group, not archive's gid",
);
}

Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown
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: 1

🤖 Fix all issues with AI agents
In @cli/tests/cli/extract/option_owner.rs:
- Around line 529-591: The test extract_with_uid_overrides_uname currently skips
if user lookup fails, which masks the bug; change the CLI invocation to pass a
definitely-nonexistent username (e.g., "user_does_not_exist_12345") for the
--uname argument and remove the conditional skip block that checks result for
"not found"/"No such" so the test requires the command to succeed even when the
uname doesn't exist; keep the rest (override_uid, skip_if_not_root!,
result.unwrap(), and the final assert that meta.uid() == override_uid) so the
--uid precedence is actually enforced.
🧹 Nitpick comments (4)
cli/tests/cli/utils/unix.rs (1)

1-11: Consider simplifying macro exposure to avoid #[macro_export] + re-export ambiguity.

If the intent is “use it as crate::utils::unix::skip_if_not_root!()”, double-check that Line 2 (#[macro_export]) isn’t unintentionally forcing the “real” path to be crate::skip_if_not_root!() (making Line 11 a workaround). If that’s the case, I’d prefer standardizing on the crate-root macro path and dropping the module re-export.

Possible direction (standardize on crate-root macro)
 #[macro_export]
 macro_rules! skip_if_not_root {
     () => {
         if !nix::unistd::Uid::effective().is_root() {
             eprintln!("Skipping test: requires root privileges");
             return;
         }
     };
 }
-pub use skip_if_not_root;
cli/tests/cli/extract/option_owner.rs (3)

22-57: Helper is fine; consider narrowing uid/gid types (and reducing duplication later).

OwnerEntryDef + create_archive_with_owner is straightforward. Minor: you could consider u32 for uid/gid fields (matching MetadataExt) and cast only at the archive boundary, to reduce mixed-width comparisons.


286-409: Avoid brittle “skip by error string”; prefer preflight user/group existence checks.

Line 328-334 and Line 390-396 skip based on message substrings (“not found”, “No such”), which is prone to churn across platforms/locales/error wrappers. Prefer checking whether the target user/group exists up front (then return;) and let real extraction failures fail the test.


120-176: Cross-platform/CI expectations: root-only coverage and “nobody/nogroup” assumptions.

  • Line 129-131 and Line 601-614 hard-code 65534 + "nobody"/"nogroup"; this is common on Linux but not universal across all unix variants.
  • Many tests are root-gated (skip), so ensure CI has at least one job that actually runs these as root; otherwise you’ll only validate the non-root paths.

Also applies to: 593-664

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4306734 and a9d677d.

📒 Files selected for processing (4)
  • cli/tests/cli/extract/option_chroot.rs
  • cli/tests/cli/extract/option_owner.rs
  • cli/tests/cli/utils.rs
  • cli/tests/cli/utils/unix.rs
🔇 Additional comments (2)
cli/tests/cli/utils.rs (1)

4-5: Unix-gated utils::unix module export looks correct.

Line 4-5 keeps unix-only test helpers from breaking non-unix builds; the provided callsites are also unix-gated.

cli/tests/cli/extract/option_chroot.rs (1)

2-2: Nice refactor: root-only test now skips cleanly (verify macro scoping).

Line 12 is a solid improvement for non-root CI runs. Please just ensure Line 2’s macro import path works reliably with #[macro_export] semantics on your MSRV and nix 0.30.1.

Also applies to: 12-12

Comment on lines +529 to +591
/// Precondition: An archive contains files with uid/gid and uname/gname.
/// Action: Extract the archive with both `--uid` and `--uname` specified.
/// Expectation: The `--uid` option takes precedence over `--uname`.
#[test]
#[cfg(unix)]
fn extract_with_uid_overrides_uname() {
setup();
skip_if_not_root!();

let override_uid = 65534;

fs::create_dir_all("extract_uid_overrides_uname").unwrap();
create_archive_with_owner(
"extract_uid_overrides_uname/archive.pna",
&[OwnerEntryDef {
path: "test.txt",
content: b"test content",
uid: 1000,
uname: "originaluser",
gid: 1000,
gname: "originalgroup",
permission: 0o644,
}],
)
.unwrap();

// Extract with both --uid and --uname (--uid should take precedence)
let result = cli::Cli::try_parse_from([
"pna",
"--quiet",
"x",
"extract_uid_overrides_uname/archive.pna",
"--overwrite",
"--out-dir",
"extract_uid_overrides_uname/out/",
"--keep-permission",
"--same-owner",
"--uid",
&override_uid.to_string(),
"--uname",
"nobody",
])
.unwrap()
.execute();

// Skip if user lookup fails
if let Err(e) = &result {
let err_str = format!("{:#}", e);
if err_str.contains("not found") || err_str.contains("No such") {
eprintln!("Skipping test: user lookup failed");
return;
}
}
result.unwrap();

let meta = fs::metadata("extract_uid_overrides_uname/out/test.txt").unwrap();

assert_eq!(
meta.uid(),
override_uid,
"--uid should take precedence over --uname"
);
}
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.

⚠️ Potential issue | 🟠 Major

--uid precedence test currently masks the bug it claims to catch.

Line 574-581 skips on user-lookup failure, but if --uid truly takes precedence over --uname, the command shouldn’t need the username to exist at all. This skip can hide a real regression.

I’d change --uname to a definitely-nonexistent name and assert the command succeeds (then assert uid == override).

Proposed fix
-    let result = cli::Cli::try_parse_from([
+    let result = cli::Cli::try_parse_from([
         "pna",
         "--quiet",
         "x",
         "extract_uid_overrides_uname/archive.pna",
         "--overwrite",
         "--out-dir",
         "extract_uid_overrides_uname/out/",
         "--keep-permission",
         "--same-owner",
         "--uid",
         &override_uid.to_string(),
         "--uname",
-        "nobody",
+        "this-user-should-not-exist-12345",
     ])
     .unwrap()
     .execute();
 
-    // Skip if user lookup fails
-    if let Err(e) = &result {
-        let err_str = format!("{:#}", e);
-        if err_str.contains("not found") || err_str.contains("No such") {
-            eprintln!("Skipping test: user lookup failed");
-            return;
-        }
-    }
-    result.unwrap();
+    // If this fails, it likely means `--uname` is still being resolved despite `--uid`.
+    result.unwrap();
🤖 Prompt for AI Agents
In @cli/tests/cli/extract/option_owner.rs around lines 529 - 591, The test
extract_with_uid_overrides_uname currently skips if user lookup fails, which
masks the bug; change the CLI invocation to pass a definitely-nonexistent
username (e.g., "user_does_not_exist_12345") for the --uname argument and remove
the conditional skip block that checks result for "not found"/"No such" so the
test requires the command to succeed even when the uname doesn't exist; keep the
rest (override_uid, skip_if_not_root!, result.unwrap(), and the final assert
that meta.uid() == override_uid) so the --uid precedence is actually enforced.

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

Labels

cli This issue is about cli application

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants