1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//! Falsification Test Generator (#121)
//!
//! Implements Karl Popper's falsificationism for installer testing.
//!
//! A claim is only scientific if it can be proven false. This module generates
//! tests designed to DISPROVE specific claims about installer behavior:
//!
//! - Idempotency: Running twice produces same state
//! - Determinism: Same inputs produce same outputs
//! - Rollback completeness: Undo restores original state
//! - Dry-run accuracy: Predictions match execution
//!
//! # Philosophy
//!
//! Traditional testing asks "does it work?" Falsificationism asks "can I break it?"
//!
//! # Example
//!
//! ```ignore
//! use bashrs::installer::{FalsificationGenerator, FalsificationHypothesis};
//!
//! let generator = FalsificationGenerator::new();
//! let hypotheses = generator.analyze_installer(&spec);
//!
//! for h in hypotheses {
//! println!("Testing: {}", h.claim);
//! let test = h.generate_test();
//! // Run test and check if hypothesis is falsified
//! }
//! ```
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// A falsifiable hypothesis about installer behavior
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FalsificationHypothesis {
/// Unique ID for this hypothesis
pub id: String,
/// Human-readable claim being tested
pub claim: String,
/// Category of the hypothesis
pub category: HypothesisCategory,
/// How to falsify this claim
pub falsification_method: String,
/// Steps involved in testing
pub step_ids: Vec<String>,
/// Expected evidence if hypothesis holds
pub expected_evidence: String,
/// Evidence that would falsify the hypothesis
pub falsifying_evidence: String,
/// Test priority (higher = more critical)
pub priority: u8,
}
/// Categories of falsifiable claims
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HypothesisCategory {
/// Step produces same state when run twice
Idempotency,
/// Same inputs always produce same outputs
Determinism,
/// Rollback fully restores previous state
RollbackCompleteness,
/// Dry-run predictions match actual execution
DryRunAccuracy,
/// Postconditions hold after step completion
PostconditionValidity,
/// Preconditions prevent invalid execution
PreconditionGuard,
/// Step completes within expected time
PerformanceBound,
/// Resource usage stays within limits
ResourceLimit,
}
impl HypothesisCategory {
/// Get human-readable description
pub fn description(&self) -> &'static str {
match self {
Self::Idempotency => "Running the operation twice produces identical state",
Self::Determinism => "Same inputs always produce identical outputs",
Self::RollbackCompleteness => "Rollback fully restores the original state",
Self::DryRunAccuracy => "Dry-run predictions match actual execution",
Self::PostconditionValidity => "Postconditions hold after step completion",
Self::PreconditionGuard => "Preconditions prevent invalid execution",
Self::PerformanceBound => "Operation completes within time limit",
Self::ResourceLimit => "Resource usage stays within specified limits",
}
}
}
/// A generated test case for falsification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FalsificationTest {
/// Test name
pub name: String,
/// Hypothesis being tested
pub hypothesis_id: String,
/// Setup actions before test
pub setup: Vec<TestAction>,
/// The test action
pub action: TestAction,
/// Verification steps
pub verification: Vec<Verification>,
/// Cleanup after test
pub cleanup: Vec<TestAction>,
}
/// An action in a test case
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TestAction {
/// Execute a step
ExecuteStep { step_id: String },
/// Execute step with specific environment
ExecuteStepWithEnv {
step_id: String,
env: HashMap<String, String>,
},
/// Capture system state
CaptureState { label: String },
/// Execute rollback
Rollback { step_id: String },
/// Execute dry-run
DryRun { step_id: String },
/// Wait for duration
Wait { duration_ms: u64 },
/// Create file with content
CreateFile { path: String, content: String },
/// Remove file
RemoveFile { path: String },
}
/// Verification to check after action
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Verification {
/// Compare two captured states
StatesEqual { state_a: String, state_b: String },
/// States must differ
StatesDiffer { state_a: String, state_b: String },
/// File exists
FileExists { path: String },
/// File does not exist
FileNotExists { path: String },
/// File has content
FileContains { path: String, content: String },
/// Command returns exit code 0
CommandSucceeds { command: String },
/// Command returns non-zero
CommandFails { command: String },
/// Duration is below threshold
DurationBelow { max_ms: u64 },
/// Dry-run matches execution
DryRunMatchesExecution { step_id: String },
}
/// Result of running a falsification test
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FalsificationResult {
/// Test that was run
pub test_name: String,
/// Hypothesis being tested
pub hypothesis_id: String,
/// Whether the hypothesis was falsified (i.e., the test found a bug)
pub falsified: bool,
/// Evidence collected
pub evidence: Vec<Evidence>,
/// Error message if test failed to run
pub error: Option<String>,
/// Duration of the test
pub duration_ms: u64,
}
/// Evidence collected during test
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Evidence {
/// What was observed
pub observation: String,
/// Expected value (if applicable)
pub expected: Option<String>,
/// Actual value (if applicable)
pub actual: Option<String>,
/// Whether this evidence supports or falsifies the hypothesis
pub supports_hypothesis: bool,
}
/// Generator for falsification tests
#[derive(Debug, Default)]
pub struct FalsificationGenerator {
/// Configuration options
config: FalsificationConfig,
}
/// Configuration for test generation
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FalsificationConfig {
/// Generate idempotency tests
pub test_idempotency: bool,
/// Generate determinism tests
pub test_determinism: bool,
/// Generate rollback tests
pub test_rollback: bool,
/// Generate dry-run tests
pub test_dry_run: bool,
/// Generate postcondition tests
pub test_postconditions: bool,
/// Generate precondition tests
pub test_preconditions: bool,
/// Generate performance tests
pub test_performance: bool,
/// Maximum number of tests per category
pub max_tests_per_category: usize,
}
impl FalsificationConfig {
/// Create config with all tests enabled
pub fn all() -> Self {
Self {
test_idempotency: true,
test_determinism: true,
test_rollback: true,
test_dry_run: true,
test_postconditions: true,
test_preconditions: true,
test_performance: true,
max_tests_per_category: 10,
}
}
/// Create minimal config for quick testing
pub fn minimal() -> Self {
Self {
test_idempotency: true,
test_determinism: true,
test_rollback: false,
test_dry_run: false,
test_postconditions: false,
test_preconditions: false,
test_performance: false,
max_tests_per_category: 5,
}
}
}
include!("falsification_falsificationgenerator.rs");