Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ cargo build --release
cargo run --release
```

## Testing

The tar application has a focused testing philosophy that separates concerns between the application (CLI interface, error handling, user experience) and the underlying `tar-rs` library (archive format correctness, encoding, permissions).

See [tests/README.md](tests/README.md) for comprehensive documentation.

```bash
# Run all tests
cargo test --all

# Run specific test
cargo test test_create_single_file
```

## License

tar is licensed under the MIT License - see the `LICENSE` file for details
41 changes: 34 additions & 7 deletions src/uu/tar/src/tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,39 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
args_vec
};

let matches = uu_app().try_get_matches_from(args_to_parse)?;
let matches = match uu_app().try_get_matches_from(args_to_parse) {
Ok(matches) => matches,
Err(err) => {
let kind = err.kind();
match kind {
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
let _ = err.print();
return Ok(());
}
_ => {
let code = match kind {
clap::error::ErrorKind::ArgumentConflict => 2,
clap::error::ErrorKind::UnknownArgument
| clap::error::ErrorKind::MissingRequiredArgument
| clap::error::ErrorKind::TooFewValues
| clap::error::ErrorKind::TooManyValues
| clap::error::ErrorKind::WrongNumberOfValues => 64,
clap::error::ErrorKind::InvalidValue
| clap::error::ErrorKind::ValueValidation => 2,
_ => 2,
};
return Err(uucore::error::USimpleError::new(code, err.to_string()));
}
}
}
};

let verbose = matches.get_flag("verbose");

// Handle extract operation
if matches.get_flag("extract") {
let archive_path = matches.get_one::<PathBuf>("file").ok_or_else(|| {
uucore::error::USimpleError::new(1, "option requires an argument -- 'f'")
uucore::error::USimpleError::new(64, "option requires an argument -- 'f'")
})?;

return operations::extract::extract_archive(archive_path, verbose);
Expand All @@ -47,7 +72,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// Handle create operation
if matches.get_flag("create") {
let archive_path = matches.get_one::<PathBuf>("file").ok_or_else(|| {
uucore::error::USimpleError::new(1, "option requires an argument -- 'f'")
uucore::error::USimpleError::new(64, "option requires an argument -- 'f'")
})?;

let files: Vec<&Path> = matches
Expand All @@ -57,7 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

if files.is_empty() {
return Err(uucore::error::USimpleError::new(
1,
2,
"Cowardly refusing to create an empty archive",
));
}
Expand All @@ -67,7 +92,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

// If no operation specified, show error
Err(uucore::error::USimpleError::new(
1,
2,
"You must specify one of the '-c' or '-x' options",
))
}
Expand All @@ -82,12 +107,14 @@ pub fn uu_app() -> Command {
.disable_help_flag(true)
.args([
// Main operation modes
arg!(-c --create "Create a new archive"),
arg!(-c --create "Create a new archive").conflicts_with("extract"),
// arg!(-d --diff "Find differences between archive and file system").alias("compare"),
// arg!(-r --append "Append files to end of archive"),
// arg!(-t --list "List contents of archive"),
// arg!(-u --update "Only append files newer than copy in archive"),
arg!(-x --extract "Extract files from archive").alias("get"),
arg!(-x --extract "Extract files from archive")
.alias("get")
.conflicts_with("create"),
// Archive file
arg!(-f --file <ARCHIVE> "Use archive file or device ARCHIVE")
.value_parser(clap::value_parser!(PathBuf)),
Expand Down
53 changes: 53 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Testing the tar Application

This directory contains tests for the `tar` command-line utility.

## Philosophy

The `tar` utility is built on top of the `tar-rs` library, which is an external crate we depend on. Because of this, we split our testing into two distinct areas:

1. **The External Library (`tar-rs`)**: This is an independent library that handles the nitty-gritty details of the tar format. If you want to verify that permissions are preserved correctly, that long paths are handled according to the UStar spec, or that unicode filenames are encoded properly, those tests belong in the `tar-rs` crate itself.

2. **The Application (`tar`)**: This is where we test the user interface. These tests ensure that the command-line arguments are parsed correctly, that the program exits with the right status codes, and that basic operations like creating and extracting archives actually work from a user's perspective.

## Writing Tests for the Application

When writing tests here, focus on the **user experience**.

* **Do** check that flags like `-c`, `-x`, `-v`, and `-f` do what they say.
* **Do** check that invalid combinations of flags produce a helpful error message and exit code 2.
* **Do** check that serious errors (like file not found) return exit code 2.
* **Do** perform "smoke tests" — create an archive and make sure the file appears; extract an archive and make sure the files come out.

* **Don't** inspect the internal bytes of the archive to verify header fields. Trust that `tar-rs` handles that.
* **Don't** write complex tests for edge cases in file system permissions or encoding, unless they are specifically related to a CLI flag.

### Example

If you are testing that `tar -cf archive.tar file.txt` works:

* **Good**: Run the command, assert it succeeds (exit code 0), and assert that `archive.tar` exists on disk.
* **Bad**: Run the command, open `archive.tar` with a library, parse the headers, and assert that the checksum is correct.

## Running Tests

You can run these tests just like any other Rust project:

```bash
cargo test --all
```

To run a specific test:

```bash
cargo test test_name
```

## Exit Codes

We follow GNU tar conventions for exit codes:

* **0**: Success.
* **1**: Some files differ (used in compare mode, not yet implemented).
* **2**: Fatal error (file not found, permission denied, conflicting options like `-c -x`, invalid option values).
* **64**: Command line usage error (unknown option, missing required argument, wrong number of values for an option).
Loading
Loading