Skip to content
Open
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 src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ private Xs3Conversion() {
/**
* Converts an XS-3 (Excess-3) number to binary.
*
* <p>Protected Invariants:
* <ul>
* <li>Each 4-bit nibble {@code (xs3 & 0xF)} represents one Excess-3 digit and is decoded by subtracting 3.</li>
* <li>Decimal place-value reconstruction uses decimal base-10 weighting {@code multiplier *= 10} (line 42).</li>
* <li>Bit-shift step {@code xs3 >>= 4} consumes exactly one 4-bit nibble per iteration.</li>
* </ul>
*
* @param xs3 The XS-3 number.
* @return The corresponding binary number.
*/
Expand All @@ -41,6 +48,13 @@ public static int xs3ToBinary(int xs3) {
/**
* Converts a binary number to XS-3 (Excess-3).
*
* <p>Protected Invariants:
* <ul>
* <li>Each decimal digit {@code (binary % 10)} is biased by +3 into its 4-bit Excess-3 representation.</li>
* <li>Nibble alignment uses 4-bit boundary scaling {@code shift * 4} (line 66) to avoid bit corruption across digits.</li>
* <li>Decimal digit consumption uses integer division {@code binary /= 10}.</li>
* </ul>
*
* @param binary The binary number.
* @return The corresponding XS-3 number.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package com.thealgorithms.bitmanipulation;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
Expand Down Expand Up @@ -62,4 +68,82 @@ public void testBinaryToXs3SingleDigit() {
int xs3 = Xs3Conversion.binaryToXs3(2);
assertEquals(0x5, xs3); // Binary 2 should convert to XS-3 0x5
}

@Test
@DisplayName("Smallest valid input zero fixture (Xs3Conversion.java:39,64)")
public void testSmallestValidInputZeroFixture() {
// Precondition: xs3 == 0 (Xs3Conversion.java:39), binary == 0 (Xs3Conversion.java:64)
assertEquals(0, Xs3Conversion.xs3ToBinary(0));
assertEquals(0x0, Xs3Conversion.binaryToXs3(0));
}

@Test
@DisplayName("Smallest valid positive single-digit fixture (Xs3Conversion.java:40,65)")
public void testSmallestValidPositiveSingleDigitFixture() {
// Precondition: binary == 1 > 0 (Xs3Conversion.java:64), digit 1 + 3 = 4 (line 65)
assertEquals(0x4, Xs3Conversion.binaryToXs3(1));
assertEquals(1, Xs3Conversion.xs3ToBinary(0x4));
}

@Test
@DisplayName("Ordinary input fixture from documentation (Xs3Conversion.java:39-44,64-69)")
public void testOrdinaryInputDocumentationFixture() {
// Precondition: positive multi-digit inputs (1234 <-> 0x4567)
int binary = 1234;
int xs3 = 0x4567;
assertEquals(xs3, Xs3Conversion.binaryToXs3(binary));
assertEquals(binary, Xs3Conversion.xs3ToBinary(xs3));
}

@Test
@DisplayName("Ordinary input two-digit fixture (Xs3Conversion.java:39-44,64-69)")
public void testOrdinaryInputTwoDigitFixture() {
// 25: 5+3=8, 2+3=5 => 0x58 (88 decimal)
int binary = 25;
int xs3 = 0x58;
assertEquals(xs3, Xs3Conversion.binaryToXs3(binary));
assertEquals(binary, Xs3Conversion.xs3ToBinary(xs3));
}

@Test
@DisplayName("Mutation resistance: protects nibble shift alignment (shift * 4, Xs3Conversion.java:66)")
public void testMutationResistanceNibbleShiftAlignment() {
// If line 66 is mutated from (shift * 4) to (shift), 25 produces 10 (0xA) instead of 88 (0x58)
assertEquals(0x58, Xs3Conversion.binaryToXs3(25));
// If line 66 is mutated to (shift), 89 produces 25 (0x19) instead of 188 (0xBC)
assertEquals(0xBC, Xs3Conversion.binaryToXs3(89));
// Multi-digit 3-digit number 100: 0+3=3, 0+3=3, 1+3=4 => 0x433
assertEquals(0x433, Xs3Conversion.binaryToXs3(100));
}

@Test
@DisplayName("Mutation resistance: protects decimal place multiplier accumulation (multiplier *= 10, Xs3Conversion.java:42)")
public void testMutationResistanceMultiplierAccumulation() {
// If line 42 is mutated from (*= 10) to (*= 16), 0x58 produces 37 instead of 25
assertEquals(25, Xs3Conversion.xs3ToBinary(0x58));
// If line 42 is mutated to (*= 16), 0xBC produces 137 instead of 89
assertEquals(89, Xs3Conversion.xs3ToBinary(0xBC));
// 0x433 should decode to 100
assertEquals(100, Xs3Conversion.xs3ToBinary(0x433));
}

@Test
@DisplayName("Bijective property across all decimal digits and multi-digit range [0, 999]")
public void testBijectiveRoundTripAllDigitsAndMultiDigits() {
for (int i = 0; i < 1000; i++) {
int xs3 = Xs3Conversion.binaryToXs3(i);
int decoded = Xs3Conversion.xs3ToBinary(xs3);
assertEquals(i, decoded, "Round-trip conversion failed for: " + i);
}
}

@Test
@DisplayName("Verify Xs3Conversion private constructor via reflection")
public void testPrivateConstructorContract() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Constructor<Xs3Conversion> constructor = Xs3Conversion.class.getDeclaredConstructor();
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
constructor.setAccessible(true);
Xs3Conversion instance = constructor.newInstance();
assertNotNull(instance);
}
}
243 changes: 243 additions & 0 deletions work/R45-T010/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
#!/usr/bin/env python3
"""
work/R45-T010/check.py
Automated verification check and mutation resistance runner for Xs3Conversion (R45-T010 / Issue #10).

Verifies that the test suite passes on the original code and reliably catches plausible
one-line mutations (nibble alignment shift and multiplier accumulation) in Xs3Conversion.
"""

import os
import re
import sys
import json
import shutil
import subprocess
from datetime import datetime

TASK_ID = "R45-T010"
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
WORK_DIR = os.path.join(BASE_DIR, "work", TASK_ID)
RESULTS_PATH = os.path.join(WORK_DIR, "results.json")
EVIDENCE_PATH = os.path.join(WORK_DIR, "evidence.md")
TARGET_SOURCE = os.path.join(
BASE_DIR, "src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java"
)

ENV = os.environ.copy()
ENV["PATH"] = f"/opt/homebrew/opt/openjdk@21/bin:/opt/homebrew/bin:{ENV.get('PATH', '')}"
ENV["JAVA_HOME"] = "/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home"


def run_command(cmd, cwd=BASE_DIR):
result = subprocess.run(
cmd,
cwd=cwd,
env=ENV,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
return result.returncode, result.stdout


def parse_test_results(output):
match = re.search(
r"Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)",
output,
)
if not match:
return None
return {
"run": int(match.group(1)),
"failures": int(match.group(2)),
"errors": int(match.group(3)),
"skipped": int(match.group(4)),
}


def main():
os.makedirs(WORK_DIR, exist_ok=True)
print(f"[*] Running {TASK_ID} mutation resistance verification in {BASE_DIR}")

# Tool versions
py_ver = sys.version
ret, java_out = run_command(["java", "-version"])
java_ver = java_out.splitlines()[0] if java_out else "unknown"

ret, mvn_out = run_command(["mvn", "-version"])
mvn_ver = mvn_out.splitlines()[0] if mvn_out else "unknown"

print(f"[*] Python: {py_ver.splitlines()[0]}")
print(f"[*] Java: {java_ver}")
print(f"[*] Maven: {mvn_ver}")

# 1. Clean native test execution
test_cmd = ["mvn", "test", "-Dtest=Xs3ConversionTest"]
print(f"[*] Executing native check (original): {' '.join(test_cmd)}")
clean_code, clean_output = run_command(test_cmd)
clean_results = parse_test_results(clean_output)

if not clean_results:
print("[!] Failed to parse test output!")
print(clean_output)
sys.exit(1)

print(
f"[*] Original test results: Tests run={clean_results['run']}, Failures={clean_results['failures']}, Errors={clean_results['errors']}"
)

# 2. Checkstyle check
checkstyle_cmd = ["mvn", "checkstyle:check"]
print(f"[*] Running checkstyle check: {' '.join(checkstyle_cmd)}")
cs_code, cs_output = run_command(checkstyle_cmd)
cs_passed = (cs_code == 0) and ("0 Checkstyle violations" in cs_output)

# 3. Read original source to ensure clean restoration
with open(TARGET_SOURCE, "r") as f:
original_code = f.read()

mutations = [
{
"id": "mutant-nibble-shift",
"name": "Omission of 4-bit nibble scaling in binaryToXs3",
"target": "xs3 |= (digit << (shift * 4));",
"mutated": "xs3 |= (digit << shift);",
"failing_test": "testMutationResistanceNibbleShiftAlignment",
},
{
"id": "mutant-multiplier-hex",
"name": "Hexadecimal scaling instead of decimal in xs3ToBinary",
"target": "multiplier *= 10;",
"mutated": "multiplier *= 16;",
"failing_test": "testMutationResistanceMultiplierAccumulation",
},
]

mutation_results = []

try:
for mut in mutations:
print(f"[*] Testing mutant: {mut['name']}")
assert mut["target"] in original_code, f"Target string not found for {mut['id']}"

mutated_code = original_code.replace(mut["target"], mut["mutated"])
with open(TARGET_SOURCE, "w") as f:
f.write(mutated_code)

m_code, m_output = run_command(test_cmd)
m_res = parse_test_results(m_output)

failed = (m_code != 0) and (m_res is not None) and (m_res["failures"] > 0)
caught_target = mut["failing_test"] in m_output

print(f" - Mutant exit code: {m_code}")
print(f" - Failures: {m_res['failures'] if m_res else 'N/A'}")
print(f" - Target regression caught mutant: {caught_target}")

mutation_results.append({
"mutant_id": mut["id"],
"name": mut["name"],
"target_line": mut["target"],
"mutated_line": mut["mutated"],
"rejected": failed and caught_target,
"failing_test": mut["failing_test"],
"exit_code": m_code,
})

assert failed and caught_target, f"Mutant {mut['id']} was NOT caught by regression tests!"
finally:
# Restore original code
with open(TARGET_SOURCE, "w") as f:
f.write(original_code)
# Re-run clean test to guarantee clean workspace
clean_code_after, clean_output_after = run_command(test_cmd)
assert clean_code_after == 0, "Failed to restore original code cleanly!"

# Save results.json
results = {
"task_id": TASK_ID,
"target_class": "com.thealgorithms.bitmanipulation.Xs3Conversion",
"target_source": "src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java",
"test_class": "com.thealgorithms.bitmanipulation.Xs3ConversionTest",
"timestamp": datetime.now().isoformat(),
"tool_versions": {
"python": py_ver.splitlines()[0],
"java": java_ver,
"maven": mvn_ver,
},
"native_command": " ".join(test_cmd),
"clean_tests": clean_results,
"checkstyle": {
"passed": cs_passed,
"exit_code": cs_code,
},
"preconditions_checked": [
{
"precondition": "Smallest valid zero fixture",
"source_line": "Xs3Conversion.java:39,64",
"inputs": "binary=0, xs3=0",
"expected": "binaryToXs3(0)=0x0, xs3ToBinary(0)=0",
},
{
"precondition": "Smallest positive single digit fixture",
"source_line": "Xs3Conversion.java:40,65",
"inputs": "binary=1, xs3=0x4",
"expected": "binaryToXs3(1)=0x4, xs3ToBinary(0x4)=1",
},
{
"precondition": "Ordinary documentation fixture",
"source_line": "Xs3Conversion.java:39-44,64-69",
"inputs": "binary=1234, xs3=0x4567",
"expected": "binaryToXs3(1234)=0x4567, xs3ToBinary(0x4567)=1234",
},
{
"precondition": "Ordinary two-digit fixture",
"source_line": "Xs3Conversion.java:39-44,64-69",
"inputs": "binary=25, xs3=0x58",
"expected": "binaryToXs3(25)=0x58, xs3ToBinary(0x58)=25",
},
],
"mutation_experiments": mutation_results,
"all_mutants_rejected": all(m["rejected"] for m in mutation_results),
}

with open(RESULTS_PATH, "w") as f:
json.dump(results, f, indent=2)
print(f"[*] Saved test results to {RESULTS_PATH}")

# Write evidence.md
with open(EVIDENCE_PATH, "w") as f:
f.write(f"# Verification Evidence: {TASK_ID}\n\n")
f.write(f"- **Target**: `src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java`\n")
f.write(f"- **Timestamp**: {datetime.now().isoformat()}\n")
f.write(f"- **Python Version**: `{py_ver.splitlines()[0]}`\n")
f.write(f"- **Java Version**: `{java_ver}`\n")
f.write(f"- **Maven Version**: `{mvn_ver}`\n\n")
f.write("## Clean Test Execution\n\n")
f.write("```\n")
f.write(f"Command: {' '.join(test_cmd)}\n")
f.write(f"Tests run: {clean_results['run']}, Failures: {clean_results['failures']}, Errors: {clean_results['errors']}, Skipped: {clean_results['skipped']}\n")
f.write("Checkstyle violations: 0\n")
f.write("```\n\n")
f.write("## Mutation Resistance Verification Matrix\n\n")
f.write("| Mutant ID | Description | Mutated Code | Catching Test | Rejected? |\n")
f.write("|---|---|---|---|---|\n")
for m in mutation_results:
f.write(f"| `{m['mutant_id']}` | {m['name']} | `{m['mutated_line']}` | `{m['failing_test']}` | **{m['rejected']}** |\n")
f.write("\n## Fixtures and Preconditions\n\n")
for p in results["preconditions_checked"]:
f.write(f"- **{p['precondition']}** (`{p['source_line']}`): {p['inputs']} -> `{p['expected']}`\n")

print(f"[*] Saved evidence to {EVIDENCE_PATH}")

assert clean_results["run"] > 0, "Test count must be non-zero"
assert clean_results["failures"] == 0, "Original tests must pass"
assert cs_passed, "Checkstyle violations found"
assert results["all_mutants_rejected"], "All mutants must be rejected by regression tests"

print("[*] All checks and mutation resistance experiments passed successfully.")


if __name__ == "__main__":
main()
29 changes: 29 additions & 0 deletions work/R45-T010/evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Verification Evidence: R45-T010

- **Target**: `src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java`
- **Timestamp**: 2026-11-11T11:40:44.683448
- **Python Version**: `3.15.2 (v3.15.2:5e58eaad7c5, Oct 25 2026, 14:19:34) [Clang 21.0.0 (clang-2100.1.1.101)]`
- **Java Version**: `openjdk version "21.0.13.0.3" 2026-10-24`
- **Maven Version**: `Apache Maven 3.10.0 (c43a36b8d67be7e0805a411bc0898af1a51f5472)`

## Clean Test Execution

```
Command: mvn test -Dtest=Xs3ConversionTest
Tests run: 14, Failures: 0, Errors: 0, Skipped: 0
Checkstyle violations: 0
```

## Mutation Resistance Verification Matrix

| Mutant ID | Description | Mutated Code | Catching Test | Rejected? |
|---|---|---|---|---|
| `mutant-nibble-shift` | Omission of 4-bit nibble scaling in binaryToXs3 | `xs3 |= (digit << shift);` | `testMutationResistanceNibbleShiftAlignment` | **True** |
| `mutant-multiplier-hex` | Hexadecimal scaling instead of decimal in xs3ToBinary | `multiplier *= 16;` | `testMutationResistanceMultiplierAccumulation` | **True** |

## Fixtures and Preconditions

- **Smallest valid zero fixture** (`Xs3Conversion.java:39,64`): binary=0, xs3=0 -> `binaryToXs3(0)=0x0, xs3ToBinary(0)=0`
- **Smallest positive single digit fixture** (`Xs3Conversion.java:40,65`): binary=1, xs3=0x4 -> `binaryToXs3(1)=0x4, xs3ToBinary(0x4)=1`
- **Ordinary documentation fixture** (`Xs3Conversion.java:39-44,64-69`): binary=1234, xs3=0x4567 -> `binaryToXs3(1234)=0x4567, xs3ToBinary(0x4567)=1234`
- **Ordinary two-digit fixture** (`Xs3Conversion.java:39-44,64-69`): binary=25, xs3=0x58 -> `binaryToXs3(25)=0x58, xs3ToBinary(0x58)=25`
Loading