| """Tests for defense robustness evaluation framework.""" |
|
|
| from __future__ import annotations |
|
|
| from unittest.mock import MagicMock |
|
|
| import torch |
|
|
| from obliteratus.analysis.defense_robustness import ( |
| DefenseProfile, |
| DefenseRobustnessEvaluator, |
| EntanglementMap, |
| SelfRepairResult, |
| ) |
|
|
|
|
| def _make_mock_pipeline(n_layers=6, hidden_dim=16, n_prompts=5): |
| """Create a mock pipeline with refusal directions and activations.""" |
| pipeline = MagicMock() |
| pipeline.model_name = "test-model" |
|
|
| |
| torch.manual_seed(42) |
| directions = {} |
| for i in range(n_layers): |
| d = torch.randn(hidden_dim) |
| directions[i] = d / d.norm() |
| pipeline.refusal_directions = directions |
|
|
| |
| harmful_means = {} |
| harmless_means = {} |
| harmful_acts = {} |
| harmless_acts = {} |
|
|
| for i in range(n_layers): |
| base = torch.randn(hidden_dim) |
| harmless_means[i] = base.unsqueeze(0) |
|
|
| |
| signal_strength = 3.0 if 2 <= i <= 4 else 0.5 |
| harmful_means[i] = (base + signal_strength * directions[i]).unsqueeze(0) |
|
|
| harmful_acts[i] = [base + signal_strength * directions[i] + torch.randn(hidden_dim) * 0.1 for _ in range(n_prompts)] |
| harmless_acts[i] = [base + torch.randn(hidden_dim) * 0.1 for _ in range(n_prompts)] |
|
|
| pipeline._harmful_means = harmful_means |
| pipeline._harmless_means = harmless_means |
| pipeline._harmful_acts = harmful_acts |
| pipeline._harmless_acts = harmless_acts |
|
|
| return pipeline |
|
|
|
|
| class TestDefenseProfile: |
| def test_profile_generates(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| profile = evaluator.profile_defense() |
|
|
| assert isinstance(profile, DefenseProfile) |
| assert profile.model_name == "test-model" |
| assert profile.refusal_layer_spread > 0 |
| assert profile.mean_refusal_strength > 0 |
| assert profile.max_refusal_strength >= profile.mean_refusal_strength |
| assert profile.estimated_robustness in ("low", "medium", "high", "very_high") |
|
|
| def test_alignment_type_estimate(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| profile = evaluator.profile_defense() |
| assert profile.alignment_type_estimate != "unknown" |
|
|
| def test_empty_pipeline(self): |
| pipeline = MagicMock() |
| pipeline.model_name = "empty" |
| pipeline.refusal_directions = {} |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| profile = evaluator.profile_defense() |
| assert profile.estimated_robustness == "unknown" |
|
|
| def test_concentration_bounded(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| profile = evaluator.profile_defense() |
| |
| assert 0 <= profile.refusal_concentration <= 1.0 |
|
|
| def test_self_repair_bounded(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| profile = evaluator.profile_defense() |
| assert 0 <= profile.self_repair_estimate <= 1.0 |
|
|
| def test_format_report(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| profile = evaluator.profile_defense() |
| report = DefenseRobustnessEvaluator.format_defense_profile(profile) |
| assert "Defense Robustness" in report |
| assert "test-model" in report |
|
|
|
|
| class TestSelfRepair: |
| def test_self_repair_measurement(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| result = evaluator.measure_self_repair(layer_idx=3) |
|
|
| assert isinstance(result, SelfRepairResult) |
| assert result.layer_idx == 3 |
| assert result.original_refusal_strength >= 0 |
| assert 0 <= result.repair_ratio <= 1.0 |
| assert len(result.compensating_layers) > 0 |
| assert 3 not in result.compensating_layers |
|
|
| def test_repair_ratio_high_for_distributed(self): |
| """Distributed refusal should have high repair ratio.""" |
| pipeline = _make_mock_pipeline(n_layers=10) |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| result = evaluator.measure_self_repair(layer_idx=3) |
| |
| assert result.repair_ratio > 0.5 |
|
|
| def test_format_self_repair(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| result = evaluator.measure_self_repair(layer_idx=2) |
| report = DefenseRobustnessEvaluator.format_self_repair(result) |
| assert "Self-Repair" in report |
| assert "Layer 2" in report |
|
|
|
|
| class TestEntanglement: |
| def test_entanglement_map(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| emap = evaluator.map_entanglement() |
|
|
| assert isinstance(emap, EntanglementMap) |
| assert len(emap.layer_entanglement) > 0 |
| assert 0 <= emap.overall_entanglement <= 1.0 |
| assert len(emap.most_entangled_layers) > 0 |
| assert len(emap.least_entangled_layers) > 0 |
|
|
| def test_capability_sensitivity_keys(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| emap = evaluator.map_entanglement() |
|
|
| expected_keys = {"factual_knowledge", "reasoning", "language_fluency", |
| "instruction_following", "math"} |
| assert set(emap.capability_sensitivity.keys()) == expected_keys |
|
|
| def test_math_most_sensitive(self): |
| """Math should be estimated as the most sensitive capability.""" |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| emap = evaluator.map_entanglement() |
| if emap.overall_entanglement > 0: |
| assert emap.capability_sensitivity["math"] >= emap.capability_sensitivity["language_fluency"] |
|
|
| def test_format_entanglement(self): |
| pipeline = _make_mock_pipeline() |
| evaluator = DefenseRobustnessEvaluator(pipeline) |
| emap = evaluator.map_entanglement() |
| report = DefenseRobustnessEvaluator.format_entanglement(emap) |
| assert "Entanglement" in report |
| assert "math" in report |
|
|