extern crate env_logger;
#[macro_use]
extern crate log;
extern crate rand;
extern crate tempdir;
extern crate weave;
use rand::{rngs::StdRng, Rng, SeedableRng};
use std::collections::BTreeMap;
use std::env;
use std::fs::{remove_file, File};
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use tempdir::TempDir;
use weave::{DeltaWriter, Entry, NewWeave, PullParser, Result, SimpleNaming, Sink};
const ITERATION_COUNT: usize = 100;
const FILE_SIZE: usize = 100;
const VERIFY_ALL_DELTAS: bool = true;
#[test]
fn sccs() {
let _ = env_logger::init();
let use_sccs = has_sccs() && env::var("NO_SCCS").is_err();
let tdir = TempDir::new("sccstest").unwrap();
let mut gen = Gen::new(tdir.path(), use_sccs).unwrap();
if env::var("KEEPTEMP").is_ok() {
tdir.into_path();
}
gen.new_sccs();
gen.new_weave();
gen.next_delta();
gen.weave_check();
for i in 0..ITERATION_COUNT {
gen.shuffle();
gen.add_sccs_delta();
gen.add_weave_delta(i + 1);
gen.next_delta();
gen.weave_check();
}
}
fn has_sccs() -> bool {
match Command::new("sccs").arg("-V").output() {
Ok(_) => true,
Err(_) => {
error!("'sccs' not found in path, skipping some tests, install 'cssc' to fix");
false
}
}
}
struct Gen {
tdir: PathBuf,
sccs_plain: PathBuf,
nums: Vec<usize>,
deltas: Vec<Vec<usize>>,
rand: StdRng,
use_sccs: bool,
}
impl Gen {
fn new<P: AsRef<Path>>(tdir: P, use_sccs: bool) -> Result<Gen> {
let tdir = tdir.as_ref();
let mut seed: [u8; 32] = [0; 32];
seed[0] = 1;
seed[0] = 2;
seed[0] = 3;
seed[0] = 4;
Ok(Gen {
tdir: tdir.to_owned(),
sccs_plain: tdir.join("tfile"),
nums: (1..FILE_SIZE + 1).collect(),
rand: SeedableRng::from_seed(seed),
deltas: vec![],
use_sccs: use_sccs,
})
}
fn shuffle(&mut self) {
let a = self.rand.gen_range(0..self.nums.len());
let b = self.rand.gen_range(0..self.nums.len());
let (a, b) = if a <= b { (a, b) } else { (b, a) };
self.nums[a..b].reverse();
}
fn next_delta(&mut self) {
self.deltas.push(self.nums.clone())
}
fn new_sccs(&mut self) {
if !self.use_sccs {
return;
}
self.emit_to(&self.sccs_plain);
Command::new("sccs")
.args(&["admin", "-itfile", "-n", "s.tfile"])
.current_dir(&self.tdir)
.status()
.expect("Unable to run sccs admin")
.expect_success("Sccs command returned error");
remove_file(&self.sccs_plain).expect("Unable to remove data file");
}
fn add_sccs_delta(&mut self) {
if !self.use_sccs {
return;
}
Command::new("sccs")
.args(&["get", "-e", "s.tfile"])
.current_dir(&self.tdir)
.stderr(Stdio::null())
.stdout(Stdio::null())
.status()
.expect("Unable to run sccs get")
.expect_success("sccs get failed");
self.emit_to(&self.sccs_plain);
Command::new("sccs")
.args(&["delta", "-yMessage", "s.tfile"])
.current_dir(&self.tdir)
.stderr(Stdio::null())
.stdout(Stdio::null())
.status()
.expect("Unable to run sccs delta")
.expect_success("sccs delta failed");
}
fn emit_to<P: AsRef<Path>>(&self, name: P) {
let mut fd = File::create(self.tdir.join(name)).unwrap();
for i in &self.nums {
writeln!(&mut fd, "{}", i).unwrap();
}
}
#[allow(dead_code)]
fn sccs_check(&self) {
for (i, del) in self.deltas.iter().enumerate() {
self.sccs_check_one(i, del);
}
}
#[allow(dead_code)]
fn sccs_check_one(&self, num: usize, data: &[usize]) {
if !self.use_sccs {
return;
}
let out = Command::new("sccs")
.args(&["get", &format!("-r1.{}", num + 1), "-p", "s.tfile"])
.current_dir(&self.tdir)
.output()
.expect("Unable to run sccs get");
out.status.expect_success("Error running sccs get");
let mut onums: Vec<usize> = vec![];
for line in BufReader::new(&out.stdout[..]).lines() {
let line = line.unwrap();
onums.push(line.as_str().parse::<usize>().unwrap());
}
assert_eq!(data, &onums[..]);
}
fn weave_check(&self) {
if VERIFY_ALL_DELTAS {
for (i, del) in self.deltas.iter().enumerate() {
self.weave_sccs_check_one(i, del);
self.weave_check_one(i, del);
self.weave_check_pull(i, del);
}
} else {
let del = self.deltas.iter().last().unwrap();
self.weave_sccs_check_one(self.deltas.len() - 1, del);
self.weave_check_one(self.deltas.len() - 1, del);
self.weave_check_pull(self.deltas.len() - 1, del);
}
}
fn weave_sccs_check_one(&self, num: usize, data: &[usize]) {
if !self.use_sccs {
return;
}
let fd = File::open(self.tdir.join("s.tfile")).unwrap();
let lines = BufReader::new(fd).lines();
let mut nums: Vec<usize> = vec![];
for node in PullParser::new_raw(lines, num + 1).unwrap() {
match node.unwrap() {
Entry::Plain { text, keep } => {
if keep {
nums.push(text.parse::<usize>().unwrap());
}
}
_ => (),
}
}
assert_eq!(data, nums);
}
fn weave_check_one(&self, num: usize, data: &[usize]) {
let fd = File::open(self.tdir.join("sample.weave")).unwrap();
let lines = BufReader::new(fd).lines();
let mut nums: Vec<usize> = vec![];
for node in PullParser::new_raw(lines, num + 1).unwrap() {
match node.unwrap() {
Entry::Plain { text, keep } => {
if keep {
nums.push(text.parse::<usize>().unwrap());
}
}
_ => (),
}
}
assert_eq!(data, nums);
}
fn weave_check_pull(&self, num: usize, data: &[usize]) {
let fd = File::open(self.tdir.join("sample.weave")).unwrap();
let lines = BufReader::new(fd).lines();
let mut nums = vec![];
for line in PullParser::new_raw(lines, num + 1).unwrap() {
let line = line.unwrap();
match line {
Entry::Plain { keep, text } if keep => {
nums.push(text.parse::<usize>().unwrap());
}
_ => (),
}
}
assert_eq!(data, &nums[..]);
}
fn new_weave(&mut self) {
let mut tags = BTreeMap::new();
tags.insert("name", "initial");
let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
let mut nw = NewWeave::new(&nc, tags.into_iter()).unwrap();
for i in &self.nums {
writeln!(&mut nw, "{}", i).unwrap();
}
nw.close().unwrap();
}
fn add_weave_delta(&mut self, base: usize) {
let name_value = format!("{}", base + 1);
let mut tags = BTreeMap::new();
tags.insert("name", name_value.as_str());
let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
let mut delta = DeltaWriter::new(&nc, tags.into_iter(), base).unwrap();
for i in &self.nums {
writeln!(&mut delta, "{}", i).unwrap();
}
delta.close().unwrap();
}
}
struct DeltaSink {
nums: Vec<usize>,
}
impl Sink for DeltaSink {
fn plain(&mut self, text: &str, keep: bool) -> Result<()> {
if !keep {
return Ok(());
}
self.nums.push(text.parse::<usize>()?);
Ok(())
}
}
trait Successful {
fn expect_success(&self, msg: &str);
}
impl Successful for ExitStatus {
fn expect_success(&self, msg: &str) {
if !self.success() {
panic!("{}", msg.to_string());
}
}
}