import json
import math
import time
from pathlib import Path
from typing import Any
import pytest
from pytest import approx
from rich import box
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from skimtoken import estimate_tokens
class TestSkimtoken:
@pytest.fixture(scope="class")
def dataset(self) -> list[dict[str, str | int | None]]:
dataset_path = Path(__file__).parent.parent / "data" / "test_dataset.jsonl"
data: list[dict[str, str | int | None]] = []
with open(dataset_path, "r", encoding="utf-8") as f:
for line in f:
if line.strip(): data.append(json.loads(line))
return data
@pytest.fixture(scope="class")
def console(self) -> Console:
return Console()
def get_error_color(self, error_rate: float) -> str:
if error_rate < 10:
return "green"
elif error_rate < 30:
return "yellow"
elif error_rate < 50:
return "orange1"
else:
return "red"
def test_comprehensive_analysis(
self, dataset: list[dict[str, str | int | None]], console: Console
) -> None:
console.print("\n")
console.print(
Panel("[bold cyan]Comprehensive Token Estimation Analysis[/bold cyan]", expand=False)
)
start_time = time.perf_counter()
results: list[dict[str, Any]] = []
category_errors: dict[str, list[float]] = {}
overall_squared_errors: list[float] = []
total_chars = 0
for entry in dataset:
text = entry["text"]
actual = entry.get("token_len")
category = entry.get("category", "unknown")
if not isinstance(text, str):
continue
if not isinstance(actual, int) or actual == 0:
continue
actual_tokens: int = actual
total_chars += len(text)
estimated_result = estimate_tokens(text)
estimated = int(estimated_result)
estimated_float = float(estimated)
actual_float = float(actual_tokens)
error: float = estimated_float - actual_float
squared_error: float = error * error
percentage_error: float = abs(error) / actual_float * 100
results.append(
{
"text": text,
"category": str(category),
"actual": actual_tokens,
"estimated": estimated,
"error": error,
"squared_error": squared_error,
"percentage_error": percentage_error,
}
)
overall_squared_errors.append(squared_error)
if isinstance(category, str):
if category not in category_errors:
category_errors[category] = []
category_errors[category].append(squared_error)
end_time = time.perf_counter()
execution_time = end_time - start_time
console.print("\n[bold]All Results:[/bold]")
table = Table(show_header=True, header_style="bold cyan", box=box.ROUNDED)
table.add_column("Text Sample", style="dim", width=40)
table.add_column("Category", width=10)
table.add_column("Actual", justify="right", width=8)
table.add_column("Estimated", justify="right", width=10)
table.add_column("Error", justify="right", width=10)
for result in results:
text_str = str(result["text"])
display_text = text_str[:37] + "..." if len(text_str) > 40 else text_str
display_text = display_text.replace("\n", " ")
error_color = self.get_error_color(float(result["percentage_error"]))
table.add_row(
display_text,
str(result["category"]),
str(result["actual"]),
str(result["estimated"]),
f"[{error_color}]{float(result['percentage_error']):.1f}%[/{error_color}]",
)
console.print(table)
overall_rmse = math.sqrt(sum(overall_squared_errors) / len(overall_squared_errors))
console.print("\n[bold]RMSE by Category:[/bold]")
cat_table = Table(show_header=True, header_style="bold cyan", box=box.ROUNDED)
cat_table.add_column("Category", style="bold")
cat_table.add_column("RMSE", justify="right")
cat_table.add_column("Samples", justify="right")
cat_table.add_column("Status", justify="center")
for category, squared_errors in sorted(category_errors.items()):
rmse = math.sqrt(sum(squared_errors) / len(squared_errors))
color = self.get_error_color(rmse * 2)
assert rmse <= 50, f"{category} RMSE too high: {rmse:.2f}"
status = "[green]✓ PASS[/green]" if rmse <= 50 else "[red]✗ FAIL[/red]"
cat_table.add_row(
category, f"[{color}]{rmse:.2f}[/{color}]", str(len(squared_errors)), status
)
console.print(cat_table)
console.print("\n[bold]Summary Statistics:[/bold]")
console.print(f"Overall RMSE: {overall_rmse:.2f} tokens")
console.print(f"Total samples processed: {len(overall_squared_errors)}")
console.print(f"Total characters: {total_chars:,}")
console.print(f"Execution time: {execution_time:.3f} seconds")
console.print(
f"Processing speed: {len(overall_squared_errors) / execution_time:.0f} samples/second"
)
console.print(f"Character throughput: {total_chars / execution_time:,.0f} chars/second")
avg_us_per_char = execution_time / total_chars * 1_000_000
console.print(f"Average per character: {avg_us_per_char:.3f}μs")
assert overall_rmse <= 100, f"Overall RMSE {overall_rmse:.2f} is outside acceptable range"
assert avg_us_per_char <= 50.0, f"Too slow: {avg_us_per_char:.3f}μs per character"
def test_edge_cases(self, dataset: list[dict[str, str | int | None]]) -> None:
edge_cases = [entry for entry in dataset if entry.get("category", "unknown") == "edge"]
for entry in edge_cases:
text = entry["text"]
if not isinstance(text, str):
continue
estimated = estimate_tokens(text)
assert estimated >= 0, f"Negative tokens for edge case: {repr(text)}"
def test_consistency(self, dataset: list[dict[str, str | int | None]]) -> None:
sample_texts = [
entry["text"]
for entry in dataset[:5]
if isinstance(entry["text"], str) and entry["text"]
]
for text in sample_texts:
results: list[int] = [estimate_tokens(text) for _ in range(3)]
assert len(set(results)) == 1, f"Inconsistent results for: {text[:30]}..."
first_result: int = results[0]
for result in results[1:]:
assert result == approx(float(first_result), rel=0), (
f"Results not identical: {first_result} vs {result}"
)