From 5c3c1dd0bd28fec0ab20d77c288f1b66b9b90a0f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 14 Jan 2019 17:19:46 -0800 Subject: [PATCH] Get logging flags working properly with test script --- .appveyor.yml | 5 +- .travis.yml | 6 +- cli/src/parse.rs | 9 +- cli/src/test.rs | 10 +- cli/src/tests/corpuses.rs | 95 +++++++++--- cli/src/tests/languages.rs | 2 + cli/src/tests/parser_api.rs | 4 - cli/src/util.rs | 55 ++++--- lib/binding/lib.rs | 2 +- script/clean | 7 - script/configure | 7 - script/configure.cmd | 3 - script/{bindgen.sh => generate-bindings} | 0 script/test | 139 +++--------------- script/test.cmd | 10 +- script/test.sh | 3 - script/trim-whitespace | 3 - .../corpus.txt | 1 + .../grammar.json | 4 +- 19 files changed, 140 insertions(+), 225 deletions(-) delete mode 100755 script/clean delete mode 100755 script/configure delete mode 100644 script/configure.cmd rename script/{bindgen.sh => generate-bindings} (100%) delete mode 100755 script/test.sh delete mode 100755 script/trim-whitespace diff --git a/.appveyor.yml b/.appveyor.yml index f46e34e6..29193a53 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,9 +10,6 @@ install: # Install dependencies - git submodule update --init -environment: - RUST_BACKTRACE: full - test_script: # Fetch and regenerate the fixture parsers - script\fetch-fixtures.cmd @@ -21,7 +18,7 @@ test_script: # Run tests - set TREE_SITTER_TEST=1 - - cargo test + - script\test.cmd branches: only: diff --git a/.travis.yml b/.travis.yml index c281ef95..5f981ce9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,6 @@ language: rust rust: - stable -env: - - RUST_BACKTRACE=full - script: # Fetch and regenerate the fixture parsers - script/fetch-fixtures @@ -12,9 +9,8 @@ script: - script/regenerate-fixtures # Run tests - - export TREE_SITTER_TEST=1 - export TREE_SITTER_STATIC_ANALYSIS=1 - - cargo test + - script/test branches: only: diff --git a/cli/src/parse.rs b/cli/src/parse.rs index fde148b6..38b6a61c 100644 --- a/cli/src/parse.rs +++ b/cli/src/parse.rs @@ -11,14 +11,13 @@ pub fn parse_file_at_path( debug: bool, debug_graph: bool, ) -> Result<()> { + let mut log_session = None; let mut parser = Parser::new(); parser.set_language(language)?; let source_code = fs::read_to_string(path)?; - let mut log_session = None; - if debug_graph { - log_session = Some(util::start_logging_graphs(&mut parser, "log.html")?); + log_session = Some(util::log_graphs(&mut parser, "log.html")?); } else if debug { parser.set_logger(Some(Box::new(|log_type, message| { if log_type == LogType::Lex { @@ -32,9 +31,7 @@ pub fn parse_file_at_path( .parse_str(&source_code, None) .expect("Incompatible language version"); - if let Some(log_session) = log_session { - util::stop_logging_graphs(&mut parser, log_session)?; - } + drop(log_session); let stdout = io::stdout(); let mut stdout = stdout.lock(); diff --git a/cli/src/test.rs b/cli/src/test.rs index e064dffd..bcea3dcc 100644 --- a/cli/src/test.rs +++ b/cli/src/test.rs @@ -44,13 +44,12 @@ pub fn run_tests_at_path( filter: Option<&str>, ) -> Result<()> { let test_entry = parse_tests(path)?; + let mut log_session = None; let mut parser = Parser::new(); parser.set_language(language)?; - let mut log_session = None; - if debug_graph { - log_session = Some(util::start_logging_graphs(&mut parser, "log.html")?); + log_session = Some(util::log_graphs(&mut parser, "log.html")?); } else if debug { parser.set_logger(Some(Box::new(|log_type, message| { if log_type == LogType::Lex { @@ -103,10 +102,7 @@ pub fn run_tests_at_path( } } - if let Some(log_session) = log_session { - util::stop_logging_graphs(&mut parser, log_session)?; - } - + drop(log_session); Ok(()) } diff --git a/cli/src/tests/corpuses.rs b/cli/src/tests/corpuses.rs index eeea113c..e1fe9189 100644 --- a/cli/src/tests/corpuses.rs +++ b/cli/src/tests/corpuses.rs @@ -2,12 +2,14 @@ use super::languages; use crate::generate; use crate::loader::Loader; use crate::test::{parse_tests, TestEntry}; +use crate::util; use std::fs; use std::path::PathBuf; -use tree_sitter::{Language, Parser}; +use tree_sitter::{Language, Parser, LogType}; lazy_static! { - static ref LANGUAGES: [(&'static str, Language); 6] = [ + static ref LANGUAGES: [(&'static str, Language); 7] = [ + ("bash", languages::bash()), ("c", languages::c()), ("cpp", languages::cpp()), ("embedded-template", languages::embedded_template()), @@ -20,45 +22,87 @@ lazy_static! { static ref SCRATCH_DIR: PathBuf = ROOT_DIR.join("target").join("scratch"); static ref FIXTURES_DIR: PathBuf = ROOT_DIR.join("test").join("fixtures"); static ref EXEC_PATH: PathBuf = std::env::current_exe().unwrap(); + static ref LANGUAGE_FILTER: Option = + std::env::var("TREE_SITTER_TEST_LANGUAGE_FILTER").ok(); + static ref EXAMPLE_FILTER: Option = + std::env::var("TREE_SITTER_TEST_EXAMPLE_FILTER").ok(); + static ref LOG_ENABLED: bool = std::env::var("TREE_SITTER_ENABLE_LOG").is_ok(); + static ref LOG_GRAPH_ENABLED: bool = std::env::var("TREE_SITTER_ENABLE_LOG_GRAPHS").is_ok(); } #[test] fn test_real_language_corpus_files() { + let mut log_session = None; let mut parser = Parser::new(); let grammars_dir = FIXTURES_DIR.join("grammars"); - for (name, language) in LANGUAGES.iter().cloned() { - let corpus_dir = grammars_dir.join(name).join("corpus"); + if *LOG_ENABLED { + parser.set_logger(Some(Box::new(|log_type, msg| { + if log_type == LogType::Lex { + eprintln!(" {}", msg); + } else { + eprintln!("{}", msg); + } + }))); + } else if *LOG_GRAPH_ENABLED { + log_session = Some(util::log_graphs(&mut parser, "log.html").unwrap()); + } + + for (language_name, language) in LANGUAGES.iter().cloned() { + if let Some(filter) = LANGUAGE_FILTER.as_ref() { + if !language_name.contains(filter.as_str()) { + continue; + } + } + + eprintln!("language: {:?}", language_name); + + let corpus_dir = grammars_dir.join(language_name).join("corpus"); let test = parse_tests(&corpus_dir).unwrap(); parser.set_language(language).unwrap(); run_mutation_tests(&mut parser, test); } + + drop(parser); + drop(log_session); } #[test] fn test_feature_corpus_files() { fs::create_dir_all(SCRATCH_DIR.as_path()).unwrap(); - let filter = std::env::var("TREE_SITTER_TEST_FILTER").ok(); - let mut loader = Loader::new(SCRATCH_DIR.clone()); + let loader = Loader::new(SCRATCH_DIR.clone()); + let mut log_session = None; let mut parser = Parser::new(); let test_grammars_dir = FIXTURES_DIR.join("test_grammars"); + if *LOG_ENABLED { + parser.set_logger(Some(Box::new(|log_type, msg| { + if log_type == LogType::Lex { + eprintln!(" {}", msg); + } else { + eprintln!("{}", msg); + } + }))); + } else if *LOG_GRAPH_ENABLED { + log_session = Some(util::log_graphs(&mut parser, "log.html").unwrap()); + } + for entry in fs::read_dir(&test_grammars_dir).unwrap() { let entry = entry.unwrap(); if !entry.metadata().unwrap().is_dir() { continue; } - let test_name = entry.file_name(); - let test_name = test_name.to_str().unwrap(); + let language_name = entry.file_name(); + let language_name = language_name.to_str().unwrap(); - if let Some(filter) = filter.as_ref() { - if !test_name.contains(filter.as_str()) { + if let Some(filter) = LANGUAGE_FILTER.as_ref() { + if !language_name.contains(filter.as_str()) { continue; } } - eprintln!("test: {:?}", test_name); + eprintln!("test language: {:?}", language_name); let test_path = entry.path(); let grammar_path = test_path.join("grammar.json"); @@ -78,13 +122,13 @@ fn test_feature_corpus_files() { } else { panic!( "Expected error message but got none for test grammar '{}'", - test_name + language_name ); } } else { let corpus_path = test_path.join("corpus.txt"); let c_code = generate_result.unwrap(); - let parser_c_path = SCRATCH_DIR.join(&format!("{}-parser.c", test_name)); + let parser_c_path = SCRATCH_DIR.join(&format!("{}-parser.c", language_name)); if !fs::read_to_string(&parser_c_path) .map(|content| content == c_code) .unwrap_or(false) @@ -98,18 +142,21 @@ fn test_feature_corpus_files() { None }; let language = loader - .load_language_from_sources(test_name, &HEADER_DIR, &parser_c_path, &scanner_path) + .load_language_from_sources( + language_name, + &HEADER_DIR, + &parser_c_path, + &scanner_path, + ) .unwrap(); let test = parse_tests(&corpus_path).unwrap(); + parser.set_language(language).unwrap(); + run_mutation_tests(&mut parser, test); } } - // for (name, language) in LANGUAGES.iter().cloned() { - // let corpus_dir = grammars_dir.join(name).join("corpus"); - // let test = parse_tests(&corpus_dir).unwrap(); - // parser.set_language(language).unwrap(); - // run_mutation_tests(&mut parser, test); - // } + drop(parser); + drop(log_session); } fn run_mutation_tests(parser: &mut Parser, test: TestEntry) { @@ -119,6 +166,14 @@ fn run_mutation_tests(parser: &mut Parser, test: TestEntry) { input, output, } => { + if let Some(filter) = EXAMPLE_FILTER.as_ref() { + if !name.contains(filter.as_str()) { + return; + } + } + + eprintln!(" example: {:?}", name); + let tree = parser .parse_utf8(&mut |byte_offset, _| &input[byte_offset..], None) .unwrap(); diff --git a/cli/src/tests/languages.rs b/cli/src/tests/languages.rs index 0c483d08..e093d218 100644 --- a/cli/src/tests/languages.rs +++ b/cli/src/tests/languages.rs @@ -1,6 +1,7 @@ use tree_sitter::Language; extern "C" { + fn tree_sitter_bash() -> Language; fn tree_sitter_c() -> Language; fn tree_sitter_cpp() -> Language; fn tree_sitter_embedded_template() -> Language; @@ -10,6 +11,7 @@ extern "C" { fn tree_sitter_rust() -> Language; } +pub fn bash() -> Language { unsafe { tree_sitter_bash() } } pub fn c() -> Language { unsafe { tree_sitter_c() } } pub fn cpp() -> Language { unsafe { tree_sitter_cpp() } } pub fn embedded_template() -> Language { unsafe { tree_sitter_embedded_template() } } diff --git a/cli/src/tests/parser_api.rs b/cli/src/tests/parser_api.rs index e32c292b..d717bfab 100644 --- a/cli/src/tests/parser_api.rs +++ b/cli/src/tests/parser_api.rs @@ -324,10 +324,6 @@ fn test_custom_utf16_input() { let mut parser = Parser::new(); parser.set_language(rust()).unwrap(); - parser.set_logger(Some(Box::new(|t, message| { - println!("log: {:?} {}", t, message); - }))); - let lines: Vec> = ["pub fn foo() {", " 1", "}"] .iter() .map(|s| s.encode_utf16().collect()) diff --git a/cli/src/util.rs b/cli/src/util.rs index f36cbe79..5c1bc39c 100644 --- a/cli/src/util.rs +++ b/cli/src/util.rs @@ -1,29 +1,28 @@ -use std::fs::File; -use std::io::{Result, Write}; +#[cfg(unix)] +use std::path::PathBuf; +#[cfg(unix)] use std::process::{Child, ChildStdin, Command, Stdio}; -use std::str; use tree_sitter::Parser; +const HTML_HEADER: &[u8] = b"\n\n\n"; + #[cfg(windows)] pub(crate) struct LogSession(); +#[cfg(unix)] +pub(crate) struct LogSession(PathBuf, Option, Option); + #[cfg(windows)] -pub(crate) fn start_logging_graphs(parser: &mut Parser, path: &str) -> Result { +pub(crate) fn log_graphs(parser: &mut Parser, path: &str) -> std::io::Result { Ok(LogSession()) } -#[cfg(windows)] -pub(crate) fn stop_logging_graphs(parser: &mut Parser, mut session: LogSession) -> Result<()> { - Ok(()) -} - #[cfg(unix)] -pub(crate) struct LogSession(Child, ChildStdin); +pub(crate) fn log_graphs(parser: &mut Parser, path: &str) -> std::io::Result { + use std::io::Write; -#[cfg(unix)] -pub(crate) fn start_logging_graphs(parser: &mut Parser, path: &str) -> Result { - let mut dot_file = File::create(path)?; - dot_file.write(b"\n\n\n")?; + let mut dot_file = std::fs::File::create(path)?; + dot_file.write(HTML_HEADER)?; let mut dot_process = Command::new("dot") .arg("-Tsvg") .stdin(Stdio::piped()) @@ -34,25 +33,23 @@ pub(crate) fn start_logging_graphs(parser: &mut Parser, path: &str) -> Result Result<()> { - drop(session.1); +impl Drop for LogSession { + fn drop(&mut self) { + use std::fs; - if cfg!(unix) { - parser.stop_printing_dot_graphs(); + drop(self.2.take().unwrap()); + let output = self.1.take().unwrap().wait_with_output().unwrap(); + if output.status.success() { + if cfg!(target_os = "macos") && fs::metadata(&self.0).unwrap().len() > HTML_HEADER.len() as u64 { + Command::new("open").arg("log.html").output().unwrap(); + } + } else { + eprintln!("Dot failed: {} {}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr)); + } } - - session.0.wait()?; - - if cfg!(target_os = "macos") { - Command::new("open").arg("log.html").output()?; - } - - Ok(()) } diff --git a/lib/binding/lib.rs b/lib/binding/lib.rs index 08f863f8..fdb243ec 100644 --- a/lib/binding/lib.rs +++ b/lib/binding/lib.rs @@ -220,7 +220,6 @@ impl Parser { unsafe { ffi::ts_parser_print_dot_graphs(self.0, ffi::dup(fd)) } } - #[cfg(unix)] pub fn stop_printing_dot_graphs(&mut self) { unsafe { ffi::ts_parser_print_dot_graphs(self.0, -1) } } @@ -391,6 +390,7 @@ impl Parser { impl Drop for Parser { fn drop(&mut self) { + self.stop_printing_dot_graphs(); self.set_logger(None); unsafe { ffi::ts_parser_delete(self.0) } } diff --git a/script/clean b/script/clean deleted file mode 100755 index dfa8ff78..00000000 --- a/script/clean +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -rm -rf \ - build out \ - gyp-mac-tool \ - Makefile *.Makefile *.target.mk \ - *.xcodeproj diff --git a/script/configure b/script/configure deleted file mode 100755 index f2e511a1..00000000 --- a/script/configure +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -e - -git submodule update --init --recursive -externals/gyp/gyp project.gyp --depth . --format=make $@ -externals/gyp/gyp tests.gyp --depth . --format=make $@ diff --git a/script/configure.cmd b/script/configure.cmd deleted file mode 100644 index dc73e8de..00000000 --- a/script/configure.cmd +++ /dev/null @@ -1,3 +0,0 @@ -git submodule update --init --recursive -call .\externals\gyp\gyp.bat project.gyp --depth . -call .\externals\gyp\gyp.bat tests.gyp --depth . diff --git a/script/bindgen.sh b/script/generate-bindings similarity index 100% rename from script/bindgen.sh rename to script/generate-bindings diff --git a/script/test b/script/test index eb394962..43c274f7 100755 --- a/script/test +++ b/script/test @@ -12,150 +12,53 @@ OPTIONS -h print this message - -b run make under scan-build static analyzer + -a Compile C code with the Clang static analyzer - -d run tests in a debugger (either lldb or gdb) + -l run only the corpus tests for the given language - -g run tests with valgrind's memcheck tool - - -G run tests with valgrind's memcheck tool, including a full leak check - - -v run tests with verbose output - - -f run only tests whose description contain the given string + -e run only the corpus tests whose name contain the given string -s set the seed used to control random behavior + -d print parsing log to stderr + -D pipe tests' stderr to \`dot(1)\` to render an SVG log EOF } -profile= -leak_check=no -mode=normal -verbose= -args=() -target=tests -export BUILDTYPE=Test -cmd="out/${BUILDTYPE}/${target}" -run_scan_build= +export TREE_SITTER_TEST=1 +export RUST_TEST_THREADS=1 +export RUST_BACKTRACE=full -if [ "$(uname -s)" == "Darwin" ]; then - export LINK="clang++ -fsanitize=address" -fi - -while getopts "bdf:s:gGhpvD" option; do +while getopts "bdl:e:s:gGhpvD" option; do case ${option} in h) usage exit ;; - d) - mode=debug + l) + export TREE_SITTER_TEST_LANGUAGE_FILTER=${OPTARG} ;; - g) - mode=valgrind - ;; - G) - mode=valgrind - leak_check=full - ;; - p) - profile=true - ;; - f) - args+=("--only=${OPTARG}") - ;; - v) - verbose=true + e) + export TREE_SITTER_TEST_EXAMPLE_FILTER=${OPTARG} ;; s) export TREE_SITTER_SEED=${OPTARG} ;; - D) - export TREE_SITTER_ENABLE_DEBUG_GRAPHS=1 - mode=SVG + d) + export TREE_SITTER_ENABLE_LOG=1 ;; - b) - run_scan_build=true + D) + export TREE_SITTER_ENABLE_LOG_GRAPHS=1 ;; esac done -if [[ -n $verbose ]]; then - args+=("--reporter=spec") +if [[ -n $TREE_SITTER_TEST_LANGUAGE_FILTER || -n $TREE_SITTER_TEST_EXAMPLE_FILTER ]]; then + top_level_filter=corpus else - args+=("--reporter=singleline") + top_level_filter=$1 fi -if [[ -n "$run_scan_build" ]]; then - . script/util/scan-build.sh - scan_build make -j2 $target -else - make -j2 $target -fi -args=${args:-""} - -if [[ -n $profile ]]; then - export CPUPROFILE=/tmp/${target}-$(date '+%s').prof -fi - -case ${mode} in - valgrind) - valgrind \ - --suppressions=./script/util/valgrind.supp \ - --dsymutil=yes \ - --leak-check=${leak_check} \ - $cmd "${args[@]}" 2>&1 | \ - grep --color -E '\w+_tests?.cc:\d+|$' - ;; - - debug) - if hash lldb &> /dev/null; then - lldb $cmd -- "${args[@]}" - elif hash gdb &> /dev/null; then - gdb $cmd -- "${args[@]}" - else - echo "No debugger found" - exit 1 - fi - ;; - - SVG) - html_file=log.html - dot_file=$html_file.dot - - function write_log_file { - echo "" > $html_file - line_count=$(grep -n '^$' $dot_file | tail -1 | cut -f1 -d:) - if [[ -n $line_count ]]; then - head -n $line_count $dot_file | dot -Tsvg >> $html_file - else - cat $dot_file | grep -v 'Assertion' | dot -Tsvg >> $html_file - fi - rm $dot_file - echo "Wrote $html_file - $line_count" - } - - function handle_sigint { - trap '' SIGINT - echo - write_log_file - exit 0 - } - trap handle_sigint SIGINT - - $cmd "${args[@]}" 2> $dot_file || export status=$? - write_log_file - exit $status - ;; - - normal) - time $cmd "${args[@]}" - ;; -esac - -if [[ -n $profile ]]; then - pprof $cmd $CPUPROFILE -fi +cargo test --jobs 1 $top_level_filter -- --nocapture diff --git a/script/test.cmd b/script/test.cmd index f2d97303..e62eed0e 100644 --- a/script/test.cmd +++ b/script/test.cmd @@ -1,9 +1,7 @@ @echo off -msbuild /p:Configuration=Test tests.vcxproj -set only_arg= -IF not "%~1"=="" ( - set only_arg=--only=%1 -) +set TREE_SITTER_TEST=1 +set RUST_TEST_THREADS=1 +set RUST_BACKTRACE=full -.\test\tests.exe --reporter=singleline --no-color %only_arg% +cargo test "%~1" diff --git a/script/test.sh b/script/test.sh deleted file mode 100755 index eb6183c0..00000000 --- a/script/test.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -RUST_TREE_SITTER_TEST=1 cargo test $@ diff --git a/script/trim-whitespace b/script/trim-whitespace deleted file mode 100755 index b67791f5..00000000 --- a/script/trim-whitespace +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -find src test include -type f | xargs perl -pi -e 's/ +$//' diff --git a/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/corpus.txt b/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/corpus.txt index 06a7bf0b..749264c6 100644 --- a/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/corpus.txt +++ b/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/corpus.txt @@ -19,6 +19,7 @@ anonymous tokens defined with LF escape sequence anonymous tokens defined with CR escape sequence ================================================= + --- (first_rule) diff --git a/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/grammar.json b/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/grammar.json index d2613776..38ada64c 100644 --- a/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/grammar.json +++ b/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/grammar.json @@ -5,10 +5,10 @@ "type": "CHOICE", "members": [ {"type": "STRING", "value": "\n"}, - {"type": "STRING", "value": "\r"}, + {"type": "STRING", "value": "\r\n"}, {"type": "STRING", "value": "'hello'"}, {"type": "PATTERN", "value": "\\d+"} ] } } -} \ No newline at end of file +}