Skip to content

Commit a427ac3

Browse files
pimpinclaude
andcommitted
Integrate automated test infrastructure in tombstone purge test
Update tombstone_purge_test.rs to use the new automated test infrastructure with Docker management and structured reporting. Changes to tombstone_purge_test.rs: - Add git status validation before test - Automatically rebuild Docker environment with correct config - Integrate TestReporter for structured output - Add checkpoints at each major step with _sync xattr capture - Generate comprehensive report in test_results/ directory - Note: Purge interval no longer set during test (STEP 4) It's now configured at bucket creation via Docker setup New example: - test_with_reporting.rs: Minimal example demonstrating the reporting infrastructure without long waits README updates: - Document automated test infrastructure features - Add check_cbs_config and tombstone_quick_check examples - Explain test report structure and contents - Update tombstone_purge_test description with automation details The test now ensures clean, reproducible runs with complete documentation for Couchbase support analysis. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 5916129 commit a427ac3

File tree

3 files changed

+359
-56
lines changed

3 files changed

+359
-56
lines changed

examples/README.md

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,55 @@ Update the file `docker-conf/db-config.json` and run
4242
$ curl -XPUT -v "http://localhost:4985/my-db/" -H 'Content-Type: application/json' --data-binary @docker-conf/db-config.json
4343
```
4444

45+
## Automated Test Infrastructure
46+
47+
The long-running tests (`tombstone_purge_test` and `tombstone_purge_test_short`) now include:
48+
49+
- **Automatic Docker environment management**: Stops, rebuilds, and starts containers with correct configuration
50+
- **Git validation**: Ensures no uncommitted changes before running
51+
- **Structured reporting**: Generates comprehensive test reports in `test_results/` directory
52+
53+
### Test Reports
54+
55+
Each test run generates a timestamped report directory containing:
56+
- `README.md`: Executive summary with test checkpoints and findings
57+
- `metadata.json`: Test metadata, commit SHA, GitHub link
58+
- `tombstone_states.json`: Full `_sync` xattr content at each checkpoint
59+
- `test_output.log`: Complete console output
60+
- `cbs_logs.log`: Couchbase Server container logs
61+
- `sgw_logs.log`: Sync Gateway container logs
62+
63+
**Example report path**: `test_results/test_run_2025-11-01_08-00-00_8db78d6/`
64+
4565
## Running an example
4666

4767
### Available examples
4868

69+
#### `check_cbs_config`
70+
Utility to verify Couchbase Server bucket configuration, especially metadata purge interval.
71+
72+
**Runtime: Instant**
73+
74+
```shell
75+
$ cargo run --features=enterprise --example check_cbs_config
76+
```
77+
78+
Expected output:
79+
```
80+
✓ CBS metadata purge interval (at purgeInterval): 0.04
81+
= 0.04 days (~1.0 hours, ~58 minutes)
82+
```
83+
84+
#### `tombstone_quick_check`
85+
Rapid validation test for tombstone detection via XATTRs. Verifies that tombstones are correctly identified in CBS without waiting for purge intervals.
86+
87+
**Runtime: ~30 seconds**
88+
**Output**: Clean, no warnings
89+
90+
```shell
91+
$ cargo run --features=enterprise --example tombstone_quick_check
92+
```
93+
4994
#### `ticket_70596`
5095
Demonstrates auto-purge behavior when documents are moved to inaccessible channels.
5196

@@ -65,22 +110,33 @@ $ cargo run --features=enterprise --example tombstone_purge_test_short
65110
#### `tombstone_purge_test`
66111
Complete tombstone purge test following Couchbase support recommendations (Thomas). Tests whether tombstones can be completely purged from CBS and SGW after the minimum 1-hour interval, such that re-creating a document with the same ID is treated as a new document.
67112

68-
**Runtime: ~65-70 minutes**
113+
**Runtime: ~65-70 minutes** (+ ~5 minutes for Docker rebuild)
114+
**Features**: Automatic Docker management, structured reporting
69115

70116
```shell
71117
$ cargo run --features=enterprise --example tombstone_purge_test
72118
```
73119

120+
**What it does automatically:**
121+
- ✅ Checks git status (fails if uncommitted changes)
122+
- ✅ Rebuilds Docker environment (docker compose down -v && up)
123+
- ✅ Verifies CBS purge interval configuration
124+
- ✅ Runs complete test with checkpoints
125+
- ✅ Generates structured report in `test_results/`
126+
- ✅ Captures CBS and SGW logs
127+
74128
**Test scenario:**
75129
1. Create document in accessible channel and replicate
76130
2. Delete document (creating tombstone)
77131
3. Purge tombstone from Sync Gateway
78-
4. Configure CBS metadata purge interval to 1 hour
132+
4. Verify CBS purge interval (configured at bucket creation)
79133
5. Wait 65 minutes
80134
6. Compact CBS and SGW
81-
7. Verify tombstone no longer exists
135+
7. Verify tombstone state (purged or persisting)
82136
8. Re-create document with same ID and verify it's treated as new (flags=0, not flags=1)
83137

138+
**Report location**: `test_results/test_run_<timestamp>_<commit_sha>/`
139+
84140
### Utility functions
85141

86142
There are utility functions available in `examples/utils/` to interact with the Sync Gateway and Couchbase Server:

examples/test_with_reporting.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
mod utils;
2+
3+
use couchbase_lite::*;
4+
use std::path::Path;
5+
use utils::*;
6+
7+
#[allow(deprecated)]
8+
fn main() {
9+
println!("=== Test with Reporting Infrastructure ===\n");
10+
11+
// STEP 0: Check git status
12+
println!("Step 0: Checking git status...");
13+
let git_info = match check_git_status() {
14+
Ok(info) => {
15+
println!("✓ Git status clean");
16+
println!(" - Commit: {}", info.commit_short_sha);
17+
println!(" - Branch: {}\n", info.branch);
18+
info
19+
}
20+
Err(e) => {
21+
eprintln!("✗ Git check failed:");
22+
eprintln!("{}", e);
23+
std::process::exit(1);
24+
}
25+
};
26+
27+
// STEP 1: Ensure clean Docker environment
28+
println!("Step 1: Setting up Docker environment...");
29+
if let Err(e) = ensure_clean_environment() {
30+
eprintln!("✗ Docker setup failed: {}", e);
31+
std::process::exit(1);
32+
}
33+
34+
// STEP 2: Initialize test reporter
35+
let mut reporter = match TestReporter::new("test_with_reporting", git_info) {
36+
Ok(r) => r,
37+
Err(e) => {
38+
eprintln!("✗ Failed to initialize reporter: {}", e);
39+
std::process::exit(1);
40+
}
41+
};
42+
43+
// STEP 3: Run actual test
44+
reporter.log("=== Starting test ===");
45+
46+
let mut db = Database::open(
47+
"test_reporting",
48+
Some(DatabaseConfiguration {
49+
directory: Path::new("./"),
50+
#[cfg(feature = "enterprise")]
51+
encryption_key: None,
52+
}),
53+
)
54+
.unwrap();
55+
56+
add_or_update_user("report_test_user", vec!["channel1".into()]);
57+
let session_token = get_session("report_test_user");
58+
59+
let mut repl = setup_replicator(db.clone(), session_token).add_document_listener(Box::new(
60+
|_dir, docs| {
61+
for doc in docs {
62+
println!(" 📡 Replicated: {} (flags={})", doc.id, doc.flags);
63+
}
64+
},
65+
));
66+
67+
repl.start(false);
68+
std::thread::sleep(std::time::Duration::from_secs(3));
69+
70+
// Create document
71+
reporter.log("\nSTEP 1: Creating document...");
72+
create_doc(&mut db, "test_doc", "channel1");
73+
std::thread::sleep(std::time::Duration::from_secs(3));
74+
75+
let state1 = get_sync_xattr("test_doc");
76+
reporter.checkpoint(
77+
"CREATED",
78+
state1.clone(),
79+
vec!["Document created in channel1".to_string()],
80+
);
81+
reporter.log("✓ Document created and replicated");
82+
83+
// Delete document
84+
reporter.log("\nSTEP 2: Deleting document...");
85+
let mut doc = db.get_document("test_doc").unwrap();
86+
db.delete_document(&mut doc).unwrap();
87+
std::thread::sleep(std::time::Duration::from_secs(3));
88+
89+
let state2 = get_sync_xattr("test_doc");
90+
reporter.checkpoint(
91+
"DELETED",
92+
state2.clone(),
93+
vec!["Document deleted, should be tombstone".to_string()],
94+
);
95+
reporter.log("✓ Document deleted");
96+
97+
// Re-create document
98+
reporter.log("\nSTEP 3: Re-creating document...");
99+
create_doc(&mut db, "test_doc", "channel1");
100+
std::thread::sleep(std::time::Duration::from_secs(3));
101+
102+
let state3 = get_sync_xattr("test_doc");
103+
reporter.checkpoint(
104+
"RECREATED",
105+
state3.clone(),
106+
vec!["Document re-created, should be live".to_string()],
107+
);
108+
reporter.log("✓ Document re-created");
109+
110+
repl.stop(None);
111+
112+
reporter.log("\n=== Test complete ===");
113+
114+
// Finalize report
115+
if let Err(e) = reporter.finalize() {
116+
eprintln!("⚠ Failed to generate report: {}", e);
117+
}
118+
}
119+
120+
#[allow(deprecated)]
121+
fn create_doc(db: &mut Database, id: &str, channel: &str) {
122+
let mut doc = Document::new_with_id(id);
123+
doc.set_properties_as_json(
124+
&serde_json::json!({
125+
"channels": channel,
126+
"test_data": "reporting test"
127+
})
128+
.to_string(),
129+
)
130+
.unwrap();
131+
db.save_document(&mut doc).unwrap();
132+
}
133+
134+
fn setup_replicator(db: Database, session_token: String) -> Replicator {
135+
let repl_conf = ReplicatorConfiguration {
136+
database: Some(db.clone()),
137+
endpoint: Endpoint::new_with_url(SYNC_GW_URL).unwrap(),
138+
replicator_type: ReplicatorType::PushAndPull,
139+
continuous: true,
140+
disable_auto_purge: false,
141+
max_attempts: 3,
142+
max_attempt_wait_time: 1,
143+
heartbeat: 60,
144+
authenticator: None,
145+
proxy: None,
146+
headers: vec![(
147+
"Cookie".to_string(),
148+
format!("SyncGatewaySession={session_token}"),
149+
)]
150+
.into_iter()
151+
.collect(),
152+
pinned_server_certificate: None,
153+
trusted_root_certificates: None,
154+
channels: MutableArray::default(),
155+
document_ids: MutableArray::default(),
156+
collections: None,
157+
accept_parent_domain_cookies: false,
158+
#[cfg(feature = "enterprise")]
159+
accept_only_self_signed_server_certificate: false,
160+
};
161+
let repl_context = ReplicationConfigurationContext::default();
162+
Replicator::new(repl_conf, Box::new(repl_context)).unwrap()
163+
}

0 commit comments

Comments
 (0)