Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
9 changes: 3 additions & 6 deletions components/clp-py-utils/clp_py_utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@ def __init__(self, path: pathlib.Path, size: int):
self.size = size
self.estimated_uncompressed_size = size

filename = path.name
if any(filename.endswith(extension) for extension in [".gz", ".gzip", ".tgz", ".tar.gz"]):
filename = path.name.lower()
if any(filename.endswith(extension) for extension in [".gz", ".gzip", ".tgz"]):
Comment thread
Bill-hbrhbr marked this conversation as resolved.
self.estimated_uncompressed_size *= 13
elif any(
filename.endswith(extension)
for extension in [".zstd", ".zstandard", ".tar.zstd", ".tar.zstandard"]
):
elif filename.endswith(".zst"):
self.estimated_uncompressed_size *= 8


Expand Down
51 changes: 47 additions & 4 deletions components/compression-coordinator/src/partition.rs
Comment thread
Bill-hbrhbr marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -240,19 +240,30 @@ impl Iterator for RoundRobinIterator {
/// The estimated uncompressed size.
fn estimate_uncompressed_size(key: &str, size: u64) -> u64 {
const GZIP_COMPRESSION_RATIO_ESTIMATE: u64 = 13;
const GZIP_SUFFIXES: &[&str] = &[".gz", ".gzip", ".tgz", ".tar.gz"];
const GZIP_SUFFIXES: &[&str] = &[".gz", ".gzip", ".tgz"];
const ZSTD_COMPRESSION_RATIO_ESTIMATE: u64 = 8;
const ZSTD_SUFFIXES: &[&str] = &[".zstd", ".zstandard", ".tar.zstd", ".tar.zstandard"];

if GZIP_SUFFIXES.iter().any(|suffix| key.ends_with(suffix)) {
if GZIP_SUFFIXES
.iter()
.any(|suffix| ends_with_ignore_ascii_case(key, suffix))
{
size * GZIP_COMPRESSION_RATIO_ESTIMATE
} else if ZSTD_SUFFIXES.iter().any(|suffix| key.ends_with(suffix)) {
} else if ends_with_ignore_ascii_case(key, ".zst") {
size * ZSTD_COMPRESSION_RATIO_ESTIMATE
} else {
size
}
}

/// # Returns
///
/// Whether `key` ends with `suffix` using ASCII case-insensitive match.
fn ends_with_ignore_ascii_case(key: &str, suffix: &str) -> bool {
let suffix_start = key.len().saturating_sub(suffix.len());
let key_suffix = key.get(suffix_start..);
key_suffix.is_some_and(|key_suffix| key_suffix.eq_ignore_ascii_case(suffix))
}

/// Gets the filename portion of an S3 object's key.
///
/// # Returns
Expand Down Expand Up @@ -393,6 +404,38 @@ mod tests {
}
}

#[test]
fn test_estimate_uncompressed_size_for_gzip_suffix() {
const FILE_SIZE: u64 = 100;

for path in [
"logs/app.log.gz",
"logs/app.log.GZ",
"logs/app.log.gzip",
"logs/app.log.GZIP",
"logs/app.log.tgz",
"logs/app.log.TGZ",
"logs/app.log.tar.gz",
"logs/app.log.TAR.GZ",
] {
assert_eq!(FILE_SIZE * 13, estimate_uncompressed_size(path, FILE_SIZE));
}
}

#[test]
fn test_estimate_uncompressed_size_for_zstandard_suffix() {
const FILE_SIZE: u64 = 100;

for path in [
"logs/app.log.zst",
"logs/app.log.clp.zst",
"logs/app.log.tar.zst",
"logs/app.log.ZST",
] {
assert_eq!(FILE_SIZE * 8, estimate_uncompressed_size(path, FILE_SIZE));
}
}
Comment on lines +425 to +437

@coderabbitai coderabbitai Bot Jul 23, 2026

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover the full Zstandard suffix contract in both test suites.

Both suites verify supported .zst variants but omit the suffixes that must no longer be recognised:

  • components/compression-coordinator/src/partition.rs#L413-L425: assert .zstd, .zstandard, .tar.zstd, and .tar.zstandard return FILE_SIZE.
  • integration-tests/tests/test_clp_native_py_project_imports.py#L28-L38: add the same negative cases and assert estimated_uncompressed_size == file_size.
📍 Affects 2 files
  • components/compression-coordinator/src/partition.rs#L413-L425 (this comment)
  • integration-tests/tests/test_clp_native_py_project_imports.py#L28-L38
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/compression-coordinator/src/partition.rs` around lines 413 - 425,
Expand the Zstandard suffix tests in
components/compression-coordinator/src/partition.rs lines 413-425 and
integration-tests/tests/test_clp_native_py_project_imports.py lines 28-38. Add
.zstd, .zstandard, .tar.zstd, and .tar.zstandard cases to both suites, asserting
estimate_uncompressed_size or estimated_uncompressed_size returns the original
file size rather than applying decompression expansion.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

omit the suffixes that must no longer be recognised

i'm not sure if that's a must. seems like adding such tests would also only add maintenance debt rather than actual value. what do you think?

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.

@junhaoliao, I think the negative cases are justified here, though I agree they should not be added merely for exhaustive extension testing.

They protect the main behavioral boundary of #2414: these suffixes were previously accepted and are now intentionally unsupported. The existing positive tests prove that .zst still works, but would not catch a later broadening back to .zstd/.zstandard—for example, if someone replaces the precise .zst check with a more permissive “zstd-like” matcher.

Since the four cases directly correspond to the removed aliases (including their .tar forms), the maintenance cost is small and the regression value is concrete. I’d keep the cases, but they can be compactly expressed as a single loop in each suite.

🐇💭


#[test]
fn test_partition_on_flush() {
const TARGET_ARCHIVE_SIZE: u64 = 1000;
Expand Down
33 changes: 33 additions & 0 deletions integration-tests/tests/test_clp_native_py_project_imports.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""Smoke tests to validate that CLP Python projects can be imported without errors."""

from pathlib import Path

from clp_mcp_server.constants import QueryJobType
from clp_package_utils.general import JobType
from clp_py_utils.clp_config import StorageEngine
from clp_py_utils.core import FileMetadata
from job_orchestration.scheduler.constants import CompressionJobStatus


Expand All @@ -20,3 +23,33 @@ def test_clp_native_py_project_enum_classes() -> None:
assert JobType.COMPRESSION == JobType("compression")
assert StorageEngine.CLP == StorageEngine("clp")
assert CompressionJobStatus.PENDING == CompressionJobStatus(0)


def test_file_metadata_estimates_zstandard_file_sizes() -> None:
"""Tests case-insensitive recognition of the standard Zstandard file extension."""
file_size = 100

for path in [
Path("app.log.zst"),
Path("app.log.clp.zst"),
Path("app.log.tar.zst"),
Path("app.log.ZST"),
]:
assert file_size * 8 == FileMetadata(path, file_size).estimated_uncompressed_size


def test_file_metadata_estimates_gzip_file_sizes() -> None:
"""Tests case-insensitive recognition of the supported gzip file extensions."""
file_size = 100

for path in [
Path("app.log.gz"),
Path("app.log.GZ"),
Path("app.log.gzip"),
Path("app.log.GZIP"),
Path("app.log.tgz"),
Path("app.log.TGZ"),
Path("app.log.tar.gz"),
Path("app.log.TAR.GZ"),
]:
assert file_size * 13 == FileMetadata(path, file_size).estimated_uncompressed_size
Loading