]> git.lizzy.rs Git - rust.git/blobdiff - tests/system.rs
Merge pull request #175 from marcusklaas/assignment
[rust.git] / tests / system.rs
index 1cdd43abbfa167eafae48565af4553217064daaa..3f63d22457693c5db08681b64889010909cab8e4 100644 (file)
@@ -19,6 +19,7 @@
 use std::io::{self, Read, BufRead, BufReader};
 use std::thread;
 use rustfmt::*;
+use rustfmt::config::Config;
 
 fn get_path_string(dir_entry: io::Result<fs::DirEntry>) -> String {
     let path = dir_entry.ok().expect("Couldn't get DirEntry.").path();
@@ -26,36 +27,44 @@ fn get_path_string(dir_entry: io::Result<fs::DirEntry>) -> String {
     path.to_str().expect("Couldn't stringify path.").to_owned()
 }
 
-// For now, the only supported regression tests are idempotent tests - the input and
-// output must match exactly.
-// FIXME(#28) would be good to check for error messages and fail on them, or at least report.
+// Integration tests. The files in the tests/source are formatted and compared
+// to their equivalent in tests/target. The target file and config can be
+// overriden by annotations in the source file. The input and output must match
+// exactly.
+// FIXME(#28) would be good to check for error messages and fail on them, or at
+// least report.
 #[test]
 fn system_tests() {
-    // Get all files in the tests/target directory
-    let files = fs::read_dir("tests/target").ok().expect("Couldn't read dir 1.");
-    let files = files.chain(fs::read_dir("tests").ok().expect("Couldn't read dir 2."));
-    let files = files.chain(fs::read_dir("src/bin").ok().expect("Couldn't read dir 3."));
+    // Get all files in the tests/source directory
+    let files = fs::read_dir("tests/source").ok().expect("Couldn't read source dir.");
     // turn a DirEntry into a String that represents the relative path to the file
     let files = files.map(get_path_string);
-    // hack because there's no `IntoIterator` impl for `[T; N]`
-    let files = files.chain(Some("src/lib.rs".to_owned()).into_iter());
 
     let (count, fails) = check_files(files);
 
     // Display results
-    println!("Ran {} idempotent tests.", count);
-    assert!(fails == 0, "{} idempotent tests failed", fails);
+    println!("Ran {} system tests.", count);
+    assert!(fails == 0, "{} system tests failed", fails);
+}
 
-    // Get all files in the tests/source directory
-    let files = fs::read_dir("tests/source").ok().expect("Couldn't read dir 4.");
+// Idempotence tests. Files in tests/target are checked to be unaltered by
+// rustfmt.
+#[test]
+fn idempotence_tests() {
+    // Get all files in the tests/target directory
+    let files = fs::read_dir("tests/target").ok().expect("Couldn't read target dir.");
+    let files = files.chain(fs::read_dir("tests").ok().expect("Couldn't read tests dir."));
+    let files = files.chain(fs::read_dir("src/bin").ok().expect("Couldn't read src dir."));
     // turn a DirEntry into a String that represents the relative path to the file
     let files = files.map(get_path_string);
+    // hack because there's no `IntoIterator` impl for `[T; N]`
+    let files = files.chain(Some("src/lib.rs".to_owned()).into_iter());
 
     let (count, fails) = check_files(files);
 
     // Display results
-    println!("Ran {} system tests.", count);
-    assert!(fails == 0, "{} system tests failed", fails);
+    println!("Ran {} idempotent tests.", count);
+    assert!(fails == 0, "{} idempotent tests failed", fails);
 }
 
 // For each file, run rustfmt and collect the output.
@@ -68,12 +77,9 @@ fn check_files<I>(files: I) -> (u32, u32)
 
     for file_name in files.filter(|f| f.ends_with(".rs")) {
         println!("Testing '{}'...", file_name);
-        match idempotent_check(file_name) {
-            Ok(()) => {},
-            Err(m) => {
-                print_mismatches(m);
-                fails += 1;
-            },
+        if let Err(msg) = idempotent_check(file_name) {
+            print_mismatches(msg);
+            fails += 1;
         }
         count += 1;
     }
@@ -91,44 +97,54 @@ fn print_mismatches(result: HashMap<String, String>) {
 static HANDLE_RESULT: &'static Fn(HashMap<String, String>) = &handle_result;
 
 pub fn idempotent_check(filename: String) -> Result<(), HashMap<String, String>> {
-    let config = get_config(&filename);
+    let sig_comments = read_significant_comments(&filename);
+    let mut config = get_config(sig_comments.get("config").map(|x| &(*x)[..]));
     let args = vec!["rustfmt".to_owned(), filename];
+
+    for (key, val) in sig_comments {
+        if key != "target" && key != "config" {
+            config.override_value(&key, &val);
+        }
+    }
+
     // this thread is not used for concurrency, but rather to workaround the issue that the passed
     // function handle needs to have static lifetime. Instead of using a global RefCell, we use
     // panic to return a result in case of failure. This has the advantage of smoothing the road to
     // multithreaded rustfmt
     thread::catch_panic(move || {
-        run(args, WriteMode::Return(HANDLE_RESULT), &config);
+        run(args, WriteMode::Return(HANDLE_RESULT), config);
     }).map_err(|any|
         *any.downcast().ok().expect("Downcast failed.")
     )
 }
 
-// Reads test config file from comments and loads it
-fn get_config(file_name: &str) -> String {
-    let config_file_name = read_significant_comment(file_name, "config")
-        .map(|file_name| {
-            let mut full_path = "tests/config/".to_owned();
-            full_path.push_str(&file_name);
-            full_path
-        })
-        .unwrap_or("default.toml".to_owned());
+
+// Reads test config file from comments and reads its contents.
+fn get_config(config_file: Option<&str>) -> Box<Config> {
+    let config_file_name = config_file.map(|file_name| {
+                                           let mut full_path = "tests/config/".to_owned();
+                                           full_path.push_str(&file_name);
+                                           full_path
+                                       })
+                                       .unwrap_or("default.toml".to_owned());
 
     let mut def_config_file = fs::File::open(config_file_name).ok().expect("Couldn't open config.");
     let mut def_config = String::new();
     def_config_file.read_to_string(&mut def_config).ok().expect("Couldn't read config.");
 
-    def_config
+    Box::new(Config::from_toml(&def_config))
 }
 
-fn read_significant_comment(file_name: &str, option: &str) -> Option<String> {
-    let file = fs::File::open(file_name).ok().expect("Couldn't read file for comment.");
+// Reads significant comments of the form: // rustfmt-key: value
+// into a hash map.
+fn read_significant_comments(file_name: &str) -> HashMap<String, String> {
+    let file = fs::File::open(file_name).ok().expect(&format!("Couldn't read file {}.", file_name));
     let reader = BufReader::new(file);
-    let pattern = format!("^\\s*//\\s*rustfmt-{}:\\s*(\\S+)", option);
+    let pattern = r"^\s*//\s*rustfmt-([^:]+):\s*(\S+)";
     let regex = regex::Regex::new(&pattern).ok().expect("Failed creating pattern 1.");
 
-    // matches exactly the lines containing significant comments or whitespace
-    let line_regex = regex::Regex::new(r"(^\s*$)|(^\s*//\s*rustfmt-[:alpha:]+:\s*\S+)")
+    // Matches lines containing significant comments or whitespace.
+    let line_regex = regex::Regex::new(r"(^\s*$)|(^\s*//\s*rustfmt-[^:]+:\s*\S+)")
         .ok().expect("Failed creating pattern 2.");
 
     reader.lines()
@@ -136,20 +152,26 @@ fn read_significant_comment(file_name: &str, option: &str) -> Option<String> {
           .take_while(|line| line_regex.is_match(&line))
           .filter_map(|line| {
               regex.captures_iter(&line).next().map(|capture| {
-                  capture.at(1).expect("Couldn't unwrap capture.").to_owned()
+                  (capture.at(1).expect("Couldn't unwrap capture.").to_owned(),
+                   capture.at(2).expect("Couldn't unwrap capture.").to_owned())
               })
           })
-          .next()
+          .collect()
 }
 
 // Compare output to input.
+// TODO: needs a better name, more explanation.
 fn handle_result(result: HashMap<String, String>) {
     let mut failures = HashMap::new();
 
     for (file_name, fmt_text) in result {
-        // If file is in tests/source, compare to file with same name in tests/target
-        let target_file_name = get_target(&file_name);
-        let mut f = fs::File::open(&target_file_name).ok().expect("Couldn't open target.");
+        // FIXME: reading significant comments again. Is there a way we can just
+        // pass the target to this function?
+        let sig_comments = read_significant_comments(&file_name);
+
+        // If file is in tests/source, compare to file with same name in tests/target.
+        let target = get_target(&file_name, sig_comments.get("target").map(|x| &(*x)[..]));
+        let mut f = fs::File::open(&target).ok().expect("Couldn't open target.");
 
         let mut text = String::new();
         // TODO: speedup by running through bytes iterator
@@ -165,15 +187,11 @@ fn handle_result(result: HashMap<String, String>) {
 }
 
 // Map source file paths to their target paths.
-fn get_target(file_name: &str) -> String {
+fn get_target(file_name: &str, target: Option<&str>) -> String {
     if file_name.starts_with("tests/source/") {
-        let target = read_significant_comment(file_name, "target");
-        let base = target.unwrap_or(file_name.trim_left_matches("tests/source/").to_owned());
-
-        let mut target_file = "tests/target/".to_owned();
-        target_file.push_str(&base);
+        let base = target.unwrap_or(file_name.trim_left_matches("tests/source/"));
 
-        target_file
+        format!("tests/target/{}", base)
     } else {
         file_name.to_owned()
     }