mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-24 15:01:38 +00:00
Implement incremental syncback mode to preserve existing file structure
- Added an `incremental` flag to the `SyncbackCommand` to allow for preserving existing file structure and middleware formats during syncback operations. - Enhanced the `syncback_loop` function to differentiate between clean mode and incremental mode, ensuring orphaned files are removed only in clean mode. - Updated logging to reflect the mode of operation during syncback. - Introduced new tests to validate the behavior of clean mode versus incremental mode, ensuring proper handling of orphaned files and preservation of existing structures.
This commit is contained in:
parent
0e5e809894
commit
58399bb863
39 changed files with 652 additions and 119 deletions
322
tests/tests/clean_mode.rs
Normal file
322
tests/tests/clean_mode.rs
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
//! Tests for clean mode (default) vs incremental mode behavior.
|
||||
//!
|
||||
//! These tests verify that:
|
||||
//! 1. Clean mode removes orphaned files
|
||||
//! 2. Clean mode doesn't remove files that are being written to
|
||||
//! 3. Clean mode doesn't remove parent directories of files being written
|
||||
//! 4. Incremental mode preserves existing file structure
|
||||
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::rojo_test::io_util::{copy_recursive, ROJO_PATH};
|
||||
|
||||
const SYNCBACK_TESTS_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/rojo-test/syncback-tests");
|
||||
|
||||
/// Helper to run syncback and return success status
|
||||
fn run_syncback(project_path: &Path, input_path: &Path, incremental: bool) -> bool {
|
||||
let mut args = vec![
|
||||
"--color",
|
||||
"never",
|
||||
"syncback",
|
||||
project_path.to_str().unwrap(),
|
||||
"--input",
|
||||
input_path.to_str().unwrap(),
|
||||
"--non-interactive",
|
||||
];
|
||||
|
||||
if incremental {
|
||||
args.push("--incremental");
|
||||
}
|
||||
|
||||
let output = Command::new(ROJO_PATH)
|
||||
.args(args)
|
||||
.output()
|
||||
.expect("Couldn't spawn syncback process");
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("Syncback failed!");
|
||||
eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
||||
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
output.status.success()
|
||||
}
|
||||
|
||||
/// Test that clean mode removes orphaned files that don't exist in the input
|
||||
#[test]
|
||||
fn clean_mode_removes_orphaned_files() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
// Use the sync_rules test case and add an orphaned file
|
||||
let source_path = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("sync_rules")
|
||||
.join("input-project");
|
||||
let input_file = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("sync_rules")
|
||||
.join("input.rbxm");
|
||||
|
||||
let test_dir = tempdir().expect("Couldn't create temporary directory");
|
||||
let project_path = test_dir.path().join("test_project");
|
||||
|
||||
fs_err::create_dir(&project_path).expect("Couldn't create project directory");
|
||||
copy_recursive(&source_path, &project_path).expect("Couldn't copy project");
|
||||
|
||||
// Create an orphaned file that doesn't match anything in the input
|
||||
let orphaned_file = project_path.join("src").join("OrphanedFile.luau");
|
||||
fs_err::write(&orphaned_file, "-- This is orphaned").expect("Couldn't create orphaned file");
|
||||
|
||||
assert!(
|
||||
orphaned_file.exists(),
|
||||
"Orphaned file should exist before syncback"
|
||||
);
|
||||
|
||||
// Run syncback in clean mode (default)
|
||||
assert!(
|
||||
run_syncback(&project_path, &input_file, false),
|
||||
"Syncback should succeed"
|
||||
);
|
||||
|
||||
// The orphaned file should be removed in clean mode
|
||||
// (the input doesn't have "OrphanedFile" script)
|
||||
assert!(
|
||||
!orphaned_file.exists(),
|
||||
"Orphaned file should be removed in clean mode"
|
||||
);
|
||||
|
||||
// The expected files should exist
|
||||
let module_file = project_path.join("src").join("module.luau");
|
||||
assert!(
|
||||
module_file.exists(),
|
||||
"module.luau should be created by syncback"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that clean mode doesn't remove files that are being written to
|
||||
#[test]
|
||||
fn clean_mode_preserves_written_files() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
// Use the child_but_not test case
|
||||
let source_path = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("child_but_not")
|
||||
.join("input-project");
|
||||
let input_file = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("child_but_not")
|
||||
.join("input.rbxl");
|
||||
|
||||
let test_dir = tempdir().expect("Couldn't create temporary directory");
|
||||
let project_path = test_dir.path().join("test_project");
|
||||
|
||||
fs_err::create_dir(&project_path).expect("Couldn't create project directory");
|
||||
copy_recursive(&source_path, &project_path).expect("Couldn't copy project");
|
||||
|
||||
// Run syncback in clean mode
|
||||
assert!(
|
||||
run_syncback(&project_path, &input_file, false),
|
||||
"Syncback should succeed"
|
||||
);
|
||||
|
||||
// The directories being written to should exist
|
||||
let only_one_copy = project_path.join("OnlyOneCopy");
|
||||
let replicated_storage = project_path.join("ReplicatedStorage");
|
||||
|
||||
assert!(
|
||||
only_one_copy.exists(),
|
||||
"OnlyOneCopy directory should exist (it's being written to)"
|
||||
);
|
||||
assert!(
|
||||
replicated_storage.exists(),
|
||||
"ReplicatedStorage directory should exist (it's being written to)"
|
||||
);
|
||||
|
||||
// The files inside should exist
|
||||
assert!(
|
||||
only_one_copy.join("child_of_one.luau").exists(),
|
||||
"child_of_one.luau should exist"
|
||||
);
|
||||
assert!(
|
||||
replicated_storage
|
||||
.join("child_replicated_storage.luau")
|
||||
.exists(),
|
||||
"child_replicated_storage.luau should exist"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that incremental mode preserves existing file structure
|
||||
#[test]
|
||||
fn incremental_mode_preserves_structure() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
// Use the sync_rules test case - it has custom extensions
|
||||
let source_path = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("sync_rules")
|
||||
.join("input-project");
|
||||
let input_file = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("sync_rules")
|
||||
.join("input.rbxm");
|
||||
|
||||
let test_dir = tempdir().expect("Couldn't create temporary directory");
|
||||
let project_path = test_dir.path().join("test_project");
|
||||
|
||||
fs_err::create_dir(&project_path).expect("Couldn't create project directory");
|
||||
copy_recursive(&source_path, &project_path).expect("Couldn't copy project");
|
||||
|
||||
// Verify the custom-extension files exist before syncback
|
||||
let modulescript = project_path.join("src").join("module.modulescript");
|
||||
let text = project_path.join("src").join("text.text");
|
||||
assert!(
|
||||
modulescript.exists(),
|
||||
"module.modulescript should exist before syncback"
|
||||
);
|
||||
assert!(text.exists(), "text.text should exist before syncback");
|
||||
|
||||
// Run syncback in incremental mode
|
||||
assert!(
|
||||
run_syncback(&project_path, &input_file, true),
|
||||
"Syncback should succeed"
|
||||
);
|
||||
|
||||
// In incremental mode, the custom extensions should be preserved
|
||||
assert!(
|
||||
modulescript.exists(),
|
||||
"module.modulescript should be preserved in incremental mode"
|
||||
);
|
||||
assert!(
|
||||
text.exists(),
|
||||
"text.text should be preserved in incremental mode"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that clean mode uses fresh file extensions (not preserving old ones)
|
||||
#[test]
|
||||
fn clean_mode_uses_fresh_extensions() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
// Use the sync_rules test case - it has custom extensions
|
||||
let source_path = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("sync_rules")
|
||||
.join("input-project");
|
||||
let input_file = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("sync_rules")
|
||||
.join("input.rbxm");
|
||||
|
||||
let test_dir = tempdir().expect("Couldn't create temporary directory");
|
||||
let project_path = test_dir.path().join("test_project");
|
||||
|
||||
fs_err::create_dir(&project_path).expect("Couldn't create project directory");
|
||||
copy_recursive(&source_path, &project_path).expect("Couldn't copy project");
|
||||
|
||||
// Run syncback in clean mode (default)
|
||||
assert!(
|
||||
run_syncback(&project_path, &input_file, false),
|
||||
"Syncback should succeed"
|
||||
);
|
||||
|
||||
// In clean mode, the files should use standard extensions
|
||||
let modulescript_old = project_path.join("src").join("module.modulescript");
|
||||
let text_old = project_path.join("src").join("text.text");
|
||||
let module_new = project_path.join("src").join("module.luau");
|
||||
let text_new = project_path.join("src").join("text.txt");
|
||||
|
||||
// Old extensions should NOT exist
|
||||
assert!(
|
||||
!modulescript_old.exists(),
|
||||
"module.modulescript should NOT exist in clean mode"
|
||||
);
|
||||
assert!(
|
||||
!text_old.exists(),
|
||||
"text.text should NOT exist in clean mode"
|
||||
);
|
||||
|
||||
// New standard extensions SHOULD exist
|
||||
assert!(
|
||||
module_new.exists(),
|
||||
"module.luau should exist in clean mode"
|
||||
);
|
||||
assert!(text_new.exists(), "text.txt should exist in clean mode");
|
||||
}
|
||||
|
||||
/// Test that clean mode doesn't remove the project file
|
||||
#[test]
|
||||
fn clean_mode_preserves_project_file() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let source_path = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("ignore_trees_adding")
|
||||
.join("input-project");
|
||||
let input_file = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("ignore_trees_adding")
|
||||
.join("input.rbxl");
|
||||
|
||||
let test_dir = tempdir().expect("Couldn't create temporary directory");
|
||||
let project_path = test_dir.path().join("test_project");
|
||||
|
||||
fs_err::create_dir(&project_path).expect("Couldn't create project directory");
|
||||
copy_recursive(&source_path, &project_path).expect("Couldn't copy project");
|
||||
|
||||
let project_file = project_path.join("default.project.json5");
|
||||
assert!(
|
||||
project_file.exists(),
|
||||
"Project file should exist before syncback"
|
||||
);
|
||||
|
||||
// Run syncback in clean mode
|
||||
assert!(
|
||||
run_syncback(&project_path, &input_file, false),
|
||||
"Syncback should succeed"
|
||||
);
|
||||
|
||||
// Project file should still exist
|
||||
assert!(
|
||||
project_file.exists(),
|
||||
"Project file should be preserved in clean mode"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that running clean mode twice produces identical results
|
||||
#[test]
|
||||
fn clean_mode_is_idempotent() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let source_path = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("child_but_not")
|
||||
.join("input-project");
|
||||
let input_file = Path::new(SYNCBACK_TESTS_PATH)
|
||||
.join("child_but_not")
|
||||
.join("input.rbxl");
|
||||
|
||||
let test_dir = tempdir().expect("Couldn't create temporary directory");
|
||||
let project_path = test_dir.path().join("test_project");
|
||||
|
||||
fs_err::create_dir(&project_path).expect("Couldn't create project directory");
|
||||
copy_recursive(&source_path, &project_path).expect("Couldn't copy project");
|
||||
|
||||
// Run syncback twice in clean mode
|
||||
assert!(
|
||||
run_syncback(&project_path, &input_file, false),
|
||||
"First syncback should succeed"
|
||||
);
|
||||
|
||||
// Capture state after first run
|
||||
let file1 = project_path.join("OnlyOneCopy").join("child_of_one.luau");
|
||||
let content1 = fs_err::read_to_string(&file1).unwrap();
|
||||
|
||||
assert!(
|
||||
run_syncback(&project_path, &input_file, false),
|
||||
"Second syncback should succeed"
|
||||
);
|
||||
|
||||
// File should still exist with same content
|
||||
assert!(
|
||||
file1.exists(),
|
||||
"File should still exist after second syncback"
|
||||
);
|
||||
let content2 = fs_err::read_to_string(&file1).unwrap();
|
||||
assert_eq!(
|
||||
content1, content2,
|
||||
"File content should be identical after second syncback"
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue