add go test feature to adapter

This commit is contained in:
kbwo 2024-06-30 18:20:27 +09:00
parent 80ee356a06
commit 07b516e798
9 changed files with 649 additions and 14 deletions

View file

@ -19,3 +19,8 @@ tree-sitter-rust = "0.21.2"
anyhow = { workspace = true }
tempfile = "3.10.1"
tree-sitter-javascript = "0.21.0"
tree-sitter-go = "0.21.0"
tracing-appender = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, default-features = false }
dirs = "5.0.1"

14
crates/adapter/src/log.rs Normal file
View file

@ -0,0 +1,14 @@
use tracing_appender::non_blocking::WorkerGuard;
pub struct Log;
impl Log {
pub fn init() -> Result<WorkerGuard, anyhow::Error> {
let home_dir = dirs::home_dir().unwrap();
let log_path = home_dir.join(".config/testing_ls_adapter/logs");
let file_appender = tracing_appender::rolling::daily(log_path, "prefix.log");
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
tracing_subscriber::fmt().with_writer(non_blocking).init();
Ok(guard)
}
}

View file

@ -1,6 +1,8 @@
use crate::model::AvailableTestKind;
use crate::model::Runner;
use anyhow::anyhow;
use clap::Parser;
use log::Log;
use std::io;
use std::io::Write;
use std::str::FromStr;
@ -9,18 +11,18 @@ use testing_language_server::spec::AdapterCommands;
use testing_language_server::spec::DetectWorkspaceArgs;
use testing_language_server::spec::DiscoverArgs;
use testing_language_server::spec::RunFileTestArgs;
pub mod log;
pub mod model;
pub mod runner;
fn pick_test_from_extra(
extra: &mut [String],
) -> Result<(Vec<String>, AvailableTestKind), anyhow::Error> {
// extraから--test-kind=のものを取り出し、元の配列から`--test-kind=`のものは除外する
let mut extra = extra.to_vec();
let index = extra
.iter()
.position(|arg| arg.starts_with("--test-kind="))
.unwrap();
.ok_or(anyhow!("test-kind is not found"))?;
let test_kind = extra.remove(index);
let language = test_kind.replace("--test-kind=", "");
@ -41,13 +43,14 @@ fn handle(commands: AdapterCommands) -> Result<(), LSError> {
}
AdapterCommands::DetectWorkspace(mut commands) => {
let (extra, test_kind) = pick_test_from_extra(&mut commands.extra)?;
test_kind.detect_workspaces_root(DetectWorkspaceArgs { extra, ..commands })?;
test_kind.detect_workspaces(DetectWorkspaceArgs { extra, ..commands })?;
Ok(())
}
}
}
fn main() {
let _guard = Log::init().expect("Failed to initialize logger");
let args = AdapterCommands::parse();
if let Err(error) = handle(args) {
io::stderr()
@ -60,10 +63,8 @@ fn main() {
mod tests {
use super::*;
use crate::runner::cargo_test::CargoTestRunner;
use crate::runner::jest::JestRunner;
#[test]
// If `--test-kind=<value>` is not present, then return Err
fn error_test_kind_detection() {
let mut extra = vec![];
pick_test_from_extra(&mut extra).unwrap_err();
@ -72,22 +73,20 @@ mod tests {
}
#[test]
// If `--test-kind=<value>` is present, then return Ok(value)
fn test_kind_detection() {
fn single_test_kind_detection() {
let mut extra = vec!["--test-kind=cargo-test".to_string()];
let (_, language) = pick_test_from_extra(&mut extra).unwrap();
assert_eq!(language, AvailableTestKind::CargoTest(CargoTestRunner));
}
#[test]
// If multiple `--test-kind=<value>` are present, then return first one
fn error_multiple_test_kind_detection() {
fn multiple_test_kind_results_first_kind() {
let mut extra = vec![
"--test-kind=cargo-test".to_string(),
"--test-kind=jest".to_string(),
"--test-kind=foo".to_string(),
];
let (_, test_kind) = pick_test_from_extra(&mut extra).unwrap();
assert_eq!(test_kind, AvailableTestKind::Jest(JestRunner));
assert_eq!(test_kind, AvailableTestKind::CargoTest(CargoTestRunner));
}
}

View file

@ -1,4 +1,5 @@
use crate::runner::cargo_test::CargoTestRunner;
use crate::runner::go::GoTestRunner;
use std::str::FromStr;
use testing_language_server::error::LSError;
use testing_language_server::spec::DetectWorkspaceArgs;
@ -11,12 +12,14 @@ use crate::runner::jest::JestRunner;
pub enum AvailableTestKind {
CargoTest(CargoTestRunner),
Jest(JestRunner),
GoTest(GoTestRunner),
}
impl Runner for AvailableTestKind {
fn disover(&self, args: DiscoverArgs) -> Result<(), LSError> {
match self {
AvailableTestKind::CargoTest(runner) => runner.disover(args),
AvailableTestKind::Jest(runner) => runner.disover(args),
AvailableTestKind::GoTest(runner) => runner.disover(args),
}
}
@ -24,13 +27,15 @@ impl Runner for AvailableTestKind {
match self {
AvailableTestKind::CargoTest(runner) => runner.run_file_test(args),
AvailableTestKind::Jest(runner) => runner.run_file_test(args),
AvailableTestKind::GoTest(runner) => runner.run_file_test(args),
}
}
fn detect_workspaces_root(&self, args: DetectWorkspaceArgs) -> Result<(), LSError> {
fn detect_workspaces(&self, args: DetectWorkspaceArgs) -> Result<(), LSError> {
match self {
AvailableTestKind::CargoTest(runner) => runner.detect_workspaces_root(args),
AvailableTestKind::Jest(runner) => runner.detect_workspaces_root(args),
AvailableTestKind::CargoTest(runner) => runner.detect_workspaces(args),
AvailableTestKind::Jest(runner) => runner.detect_workspaces(args),
AvailableTestKind::GoTest(runner) => runner.detect_workspaces(args),
}
}
}
@ -42,6 +47,7 @@ impl FromStr for AvailableTestKind {
match s {
"cargo-test" => Ok(AvailableTestKind::CargoTest(CargoTestRunner)),
"jest" => Ok(AvailableTestKind::Jest(JestRunner)),
"go-test" => Ok(AvailableTestKind::GoTest(GoTestRunner)),
_ => Err(anyhow::anyhow!("Unknown test kind: {}", s)),
}
}
@ -50,5 +56,5 @@ impl FromStr for AvailableTestKind {
pub trait Runner {
fn disover(&self, args: DiscoverArgs) -> Result<(), LSError>;
fn run_file_test(&self, args: RunFileTestArgs) -> Result<(), LSError>;
fn detect_workspaces_root(&self, args: DetectWorkspaceArgs) -> Result<(), LSError>;
fn detect_workspaces(&self, args: DetectWorkspaceArgs) -> Result<(), LSError>;
}

View file

@ -0,0 +1,383 @@
use crate::model::Runner;
use anyhow::anyhow;
use lsp_types::Diagnostic;
use lsp_types::Position;
use lsp_types::Range;
use regex::Regex;
use serde::Deserialize;
use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Output;
use std::str::FromStr;
use testing_language_server::error::LSError;
use testing_language_server::spec::DiscoverResult;
use testing_language_server::spec::DiscoverResultItem;
use testing_language_server::spec::RunFileTestResult;
use testing_language_server::spec::RunFileTestResultItem;
use testing_language_server::spec::TestItem;
use tree_sitter::Point;
use tree_sitter::Query;
use tree_sitter::QueryCursor;
use super::util::detect_workspaces_from_file_paths;
// If the character value is greater than the line length it defaults back to the line length.
const MAX_CHAR_LENGTH: u32 = 10000;
#[derive(Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
enum Action {
Start,
Run,
Output,
Fail,
Pass,
}
#[allow(dead_code)]
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct TestResultLine {
time: String,
action: Action,
package: String,
test: Option<String>,
output: Option<String>,
}
fn get_position_from_output(output: &str) -> Option<(String, u32)> {
let pattern = r"^\s{4}(.*_test\.go):(\d+):";
let re = Regex::new(pattern).unwrap();
if let Some(captures) = re.captures(output) {
if let (Some(file_name), Some(lnum)) = (captures.get(1), captures.get(2)) {
return Some((
file_name.as_str().to_string(),
lnum.as_str().parse::<u32>().unwrap() - 1,
));
}
}
None
}
fn get_log_from_output(output: &str) -> String {
output.replace(" ", "")
}
fn parse_diagnostics(
contents: &str,
workspace_root: PathBuf,
file_paths: &[String],
) -> Result<RunFileTestResult, LSError> {
let contents = contents.replace("\r\n", "\n");
let lines = contents.lines();
let mut result_map: HashMap<String, Vec<Diagnostic>> = HashMap::new();
let mut file_name: Option<String> = None;
let mut lnum: Option<u32> = None;
let mut message = String::new();
let mut last_action: Option<Action> = None;
for line in lines {
let value: TestResultLine = serde_json::from_str(line).map_err(|e| anyhow!("{:?}", e))?;
match value.action {
Action::Run => {
file_name = None;
message = String::new();
}
Action::Output => {
let output = &value.output.unwrap();
if let Some((detected_file_name, detected_lnum)) = get_position_from_output(output)
{
file_name = Some(detected_file_name);
lnum = Some(detected_lnum);
message = String::new();
} else {
message += &get_log_from_output(output);
}
}
_ => {}
}
let current_action = value.action;
let is_action_changed = last_action.as_ref() != Some(&current_action);
if is_action_changed {
last_action = Some(current_action);
} else {
continue;
}
if let (Some(detected_fn), Some(detected_lnum)) = (&file_name, lnum) {
let diagnostic = Diagnostic {
range: Range {
start: Position {
line: detected_lnum,
character: 1,
},
end: Position {
line: detected_lnum,
character: MAX_CHAR_LENGTH,
},
},
message: message.clone(),
..Diagnostic::default()
};
let file_path = workspace_root
.join(detected_fn)
.to_str()
.unwrap()
.to_owned();
if file_paths.contains(&file_path) {
result_map.entry(file_path).or_default().push(diagnostic);
}
file_name = None;
lnum = None;
}
}
Ok(result_map
.into_iter()
.map(|(path, diagnostics)| RunFileTestResultItem { path, diagnostics })
.collect())
}
fn discover(file_path: &str) -> Result<Vec<TestItem>, LSError> {
let mut parser = tree_sitter::Parser::new();
let mut test_items: Vec<TestItem> = vec![];
parser
.set_language(&tree_sitter_go::language())
.expect("Error loading Rust grammar");
let source_code = std::fs::read_to_string(file_path)?;
let tree = parser.parse(&source_code, None).unwrap();
let query_string = r#"
;;query
((function_declaration
name: (identifier) @test.name)
(#match? @test.name "^(Test|Example)"))
@test.definition
(method_declaration
name: (field_identifier) @test.name
(#match? @test.name "^(Test|Example)")) @test.definition
(call_expression
function: (selector_expression
field: (field_identifier) @test.method)
(#match? @test.method "^Run$")
arguments: (argument_list . (interpreted_string_literal) @test.name))
@test.definition
;; query for list table tests
(block
(short_var_declaration
left: (expression_list
(identifier) @test.cases)
right: (expression_list
(composite_literal
(literal_value
(literal_element
(literal_value
(keyed_element
(literal_element
(identifier) @test.field.name)
(literal_element
(interpreted_string_literal) @test.name)))) @test.definition))))
(for_statement
(range_clause
left: (expression_list
(identifier) @test.case)
right: (identifier) @test.cases1
(#eq? @test.cases @test.cases1))
body: (block
(expression_statement
(call_expression
function: (selector_expression
field: (field_identifier) @test.method)
(#match? @test.method "^Run$")
arguments: (argument_list
(selector_expression
operand: (identifier) @test.case1
(#eq? @test.case @test.case1)
field: (field_identifier) @test.field.name1
(#eq? @test.field.name @test.field.name1))))))))
;; query for map table tests
(block
(short_var_declaration
left: (expression_list
(identifier) @test.cases)
right: (expression_list
(composite_literal
(literal_value
(keyed_element
(literal_element
(interpreted_string_literal) @test.name)
(literal_element
(literal_value) @test.definition))))))
(for_statement
(range_clause
left: (expression_list
((identifier) @test.key.name)
((identifier) @test.case))
right: (identifier) @test.cases1
(#eq? @test.cases @test.cases1))
body: (block
(expression_statement
(call_expression
function: (selector_expression
field: (field_identifier) @test.method)
(#match? @test.method "^Run$")
arguments: (argument_list
((identifier) @test.key.name1
(#eq? @test.key.name @test.key.name1))))))))
"#;
let query =
Query::new(&tree_sitter_go::language(), query_string).expect("Error creating query");
let mut cursor = QueryCursor::new();
cursor.set_byte_range(tree.root_node().byte_range());
let source = source_code.as_bytes();
let matches = cursor.matches(&query, tree.root_node(), source);
for m in matches {
let mut namespace_name = "";
let mut test_start_position = Point::default();
let mut test_end_position = Point::default();
for capture in m.captures {
let capture_name = query.capture_names()[capture.index as usize];
let value = capture.node.utf8_text(source)?;
let start_position = capture.node.start_position();
let end_position = capture.node.end_position();
match capture_name {
"namespace.name" => {
namespace_name = value;
}
"test.definition" => {
test_start_position = start_position;
test_end_position = end_position;
}
"test.name" => {
let test_name = value;
let test_item = TestItem {
id: format!("{}:{}", namespace_name, test_name),
name: test_name.to_string(),
start_position: Range {
start: Position {
line: test_start_position.row as u32,
character: test_start_position.column as u32,
},
end: Position {
line: test_start_position.row as u32,
character: MAX_CHAR_LENGTH,
},
},
end_position: Range {
start: Position {
line: test_end_position.row as u32,
character: 0,
},
end: Position {
line: test_end_position.row as u32,
character: test_end_position.column as u32,
},
},
};
test_items.push(test_item);
test_start_position = Point::default();
test_end_position = Point::default();
}
_ => {}
}
}
}
Ok(test_items)
}
#[derive(Eq, PartialEq, Hash, Debug)]
pub struct GoTestRunner;
impl Runner for GoTestRunner {
fn disover(
&self,
args: testing_language_server::spec::DiscoverArgs,
) -> Result<(), testing_language_server::error::LSError> {
let file_paths = args.file_paths;
let mut discover_results: DiscoverResult = vec![];
for file_path in file_paths {
let tests = discover(&file_path)?;
discover_results.push(DiscoverResultItem {
tests,
path: file_path,
});
}
serde_json::to_writer(std::io::stdout(), &discover_results)?;
Ok(())
}
fn run_file_test(
&self,
args: testing_language_server::spec::RunFileTestArgs,
) -> Result<(), testing_language_server::error::LSError> {
let file_paths = args.file_paths;
let default_args = ["-v", "-json", "", "-count=1", "-timeout=60s"];
let workspace = args.workspace;
let test_result = std::process::Command::new("go")
.current_dir(&workspace)
.arg("test")
.args(default_args)
.args(args.extra)
.output()
.unwrap();
let Output { stdout, stderr, .. } = test_result;
if stdout.is_empty() && !stderr.is_empty() {
return Err(LSError::Adapter(String::from_utf8(stderr).unwrap()));
}
let test_result = String::from_utf8(stdout)?;
let diagnostics: RunFileTestResult = parse_diagnostics(
&test_result,
PathBuf::from_str(&workspace).unwrap(),
&file_paths,
)?;
serde_json::to_writer(std::io::stdout(), &diagnostics)?;
Ok(())
}
fn detect_workspaces(
&self,
args: testing_language_server::spec::DetectWorkspaceArgs,
) -> Result<(), testing_language_server::error::LSError> {
serde_json::to_writer(
std::io::stdout(),
&detect_workspaces_from_file_paths(&args.file_paths, &["go.mod".to_string()]),
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::runner::go::discover;
use std::str::FromStr;
use std::{fs::read_to_string, path::PathBuf};
use crate::runner::go::parse_diagnostics;
#[test]
fn test_parse_diagnostics() {
let current_dir = std::env::current_dir().unwrap();
let test_file_path = current_dir.join("tests/go-test.txt");
let contents = read_to_string(test_file_path).unwrap();
let workspace = PathBuf::from_str("/home/demo/test/go/src/test").unwrap();
let target_file_path = "/home/demo/test/go/src/test/cases_test.go";
let result =
parse_diagnostics(&contents, workspace, &[target_file_path.to_string()]).unwrap();
let result = result.first().unwrap();
assert_eq!(result.path, target_file_path);
let diagnostic = result.diagnostics.first().unwrap();
assert_eq!(diagnostic.range.start.line, 30);
assert_eq!(diagnostic.range.start.character, 1);
assert_eq!(diagnostic.range.end.line, 30);
assert_eq!(diagnostic.message, "\tError Trace:\tcases_test.go:31\n\tError: \tNot equal: \n\t \texpected: 7\n\t \tactual : -1\n\tTest: \tTestSubtract/test_two\n--- FAIL: TestSubtract (0.00s)\n --- FAIL: TestSubtract/test_one (0.00s)\n");
}
#[test]
fn test_discover() {
let file_path = "../../test_proj/go/cases_test.go";
let test_items = discover(file_path).unwrap();
assert!(!test_items.is_empty());
}
}

View file

@ -1,2 +1,4 @@
pub mod cargo_test;
pub mod go;
pub mod jest;
pub mod util;

View file

@ -0,0 +1,50 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
/// determine if a particular file is the root of workspace based on whether it is in the same directory
pub fn detect_workspace_from_file(file_path: PathBuf, file_names: &[String]) -> Option<String> {
let parent = file_path.parent();
if let Some(parent) = parent {
if file_names
.iter()
.any(|file_name| parent.join(file_name).exists())
{
return Some(parent.to_string_lossy().to_string());
} else {
detect_workspace_from_file(parent.to_path_buf(), file_names)
}
} else {
None
}
}
pub fn detect_workspaces_from_file_paths(
target_file_paths: &[String],
file_names: &[String],
) -> HashMap<String, Vec<String>> {
let mut result_map: HashMap<String, Vec<String>> = HashMap::new();
let mut file_paths = target_file_paths.to_vec();
file_paths.sort_by_key(|b| std::cmp::Reverse(b.len()));
for file_path in file_paths {
let existing_workspace = result_map
.iter()
.find(|(workspace_root, _)| file_path.contains(workspace_root.as_str()));
if let Some((workspace_root, _)) = existing_workspace {
result_map
.entry(workspace_root.to_string())
.or_default()
.push(file_path.clone());
} else {
let workspace =
detect_workspace_from_file(PathBuf::from_str(&file_path).unwrap(), file_names);
if let Some(workspace) = workspace {
result_map
.entry(workspace)
.or_default()
.push(file_path.clone());
}
}
}
result_map
}

View file

@ -0,0 +1,161 @@
{"Time":"2024-05-25T17:06:16.98464582+09:00","Action":"start","Package":"neotest_go"}
{"Time":"2024-05-25T17:06:16.986822201+09:00","Action":"run","Package":"neotest_go","Test":"TestSubtract"}
{"Time":"2024-05-25T17:06:16.986838849+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract","Output":"=== RUN TestSubtract\n"}
{"Time":"2024-05-25T17:06:16.986859373+09:00","Action":"run","Package":"neotest_go","Test":"TestSubtract/test_one"}
{"Time":"2024-05-25T17:06:16.98686856+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_one","Output":" \tError Trace:\tcases_test.go:31\n"}
{"Time":"2024-05-25T17:06:16.986871386+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_one","Output":" \tError: \tNot equal: \n"}
{"Time":"2024-05-25T17:06:16.986874139+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_one","Output":" \t \texpected: 3\n"}
{"Time":"2024-05-25T17:06:16.986876748+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_one","Output":" \t \tactual : -1\n"}
{"Time":"2024-05-25T17:06:16.986879547+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_one","Output":" \tTest: \tTestSubtract/test_one\n"}
{"Time":"2024-05-25T17:06:16.986883029+09:00","Action":"run","Package":"neotest_go","Test":"TestSubtract/test_two"}
{"Time":"2024-05-25T17:06:16.986885264+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_two","Output":"=== RUN TestSubtract/test_two\n"}
{"Time":"2024-05-25T17:06:16.986888429+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_two","Output":" cases_test.go:31: \n"}
{"Time":"2024-05-25T17:06:16.986891613+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_two","Output":" \tError Trace:\tcases_test.go:31\n"}
{"Time":"2024-05-25T17:06:16.986894222+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_two","Output":" \tError: \tNot equal: \n"}
{"Time":"2024-05-25T17:06:16.986896835+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_two","Output":" \t \texpected: 7\n"}
{"Time":"2024-05-25T17:06:16.986899333+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_two","Output":" \t \tactual : -1\n"}
{"Time":"2024-05-25T17:06:16.986901904+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_two","Output":" \tTest: \tTestSubtract/test_two\n"}
{"Time":"2024-05-25T17:06:16.986906401+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract","Output":"--- FAIL: TestSubtract (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.986910144+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_one","Output":" --- FAIL: TestSubtract/test_one (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.986913275+09:00","Action":"fail","Package":"neotest_go","Test":"TestSubtract/test_one","Elapsed":0}
{"Time":"2024-05-25T17:06:16.986916945+09:00","Action":"output","Package":"neotest_go","Test":"TestSubtract/test_two","Output":" --- FAIL: TestSubtract/test_two (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.986919709+09:00","Action":"fail","Package":"neotest_go","Test":"TestSubtract/test_two","Elapsed":0}
{"Time":"2024-05-25T17:06:16.986922033+09:00","Action":"fail","Package":"neotest_go","Test":"TestSubtract","Elapsed":0}
{"Time":"2024-05-25T17:06:16.986924322+09:00","Action":"run","Package":"neotest_go","Test":"TestAdd"}
{"Time":"2024-05-25T17:06:16.986926439+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd","Output":"=== RUN TestAdd\n"}
{"Time":"2024-05-25T17:06:16.986929637+09:00","Action":"run","Package":"neotest_go","Test":"TestAdd/test_one"}
{"Time":"2024-05-25T17:06:16.986931891+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_one","Output":"=== RUN TestAdd/test_one\n"}
{"Time":"2024-05-25T17:06:16.98693449+09:00","Action":"run","Package":"neotest_go","Test":"TestAdd/test_two"}
{"Time":"2024-05-25T17:06:16.986936644+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_two","Output":"=== RUN TestAdd/test_two\n"}
{"Time":"2024-05-25T17:06:16.986939093+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_two","Output":" cases_test.go:42: \n"}
{"Time":"2024-05-25T17:06:16.986941721+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_two","Output":" \tError Trace:\tcases_test.go:42\n"}
{"Time":"2024-05-25T17:06:16.986944261+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_two","Output":" \tError: \tNot equal: \n"}
{"Time":"2024-05-25T17:06:16.986946773+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_two","Output":" \t \texpected: 5\n"}
{"Time":"2024-05-25T17:06:16.986949247+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_two","Output":" \t \tactual : 3\n"}
{"Time":"2024-05-25T17:06:16.986951706+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_two","Output":" \tTest: \tTestAdd/test_two\n"}
{"Time":"2024-05-25T17:06:16.986954288+09:00","Action":"run","Package":"neotest_go","Test":"TestAdd/string"}
{"Time":"2024-05-25T17:06:16.986956496+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/string","Output":"=== RUN TestAdd/string\n"}
{"Time":"2024-05-25T17:06:16.986959568+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd","Output":"--- FAIL: TestAdd (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.986964387+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_one","Output":" --- PASS: TestAdd/test_one (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.986967557+09:00","Action":"pass","Package":"neotest_go","Test":"TestAdd/test_one","Elapsed":0}
{"Time":"2024-05-25T17:06:16.986970137+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/test_two","Output":" --- FAIL: TestAdd/test_two (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.986973398+09:00","Action":"fail","Package":"neotest_go","Test":"TestAdd/test_two","Elapsed":0}
{"Time":"2024-05-25T17:06:16.986976554+09:00","Action":"output","Package":"neotest_go","Test":"TestAdd/string","Output":" --- PASS: TestAdd/string (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.986979769+09:00","Action":"pass","Package":"neotest_go","Test":"TestAdd/string","Elapsed":0}
{"Time":"2024-05-25T17:06:16.98698262+09:00","Action":"fail","Package":"neotest_go","Test":"TestAdd","Elapsed":0}
{"Time":"2024-05-25T17:06:16.98698541+09:00","Action":"run","Package":"neotest_go","Test":"TestAddOne"}
{"Time":"2024-05-25T17:06:16.986987976+09:00","Action":"output","Package":"neotest_go","Test":"TestAddOne","Output":"=== RUN TestAddOne\n"}
{"Time":"2024-05-25T17:06:16.986990912+09:00","Action":"output","Package":"neotest_go","Test":"TestAddOne","Output":"--- PASS: TestAddOne (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.986994224+09:00","Action":"pass","Package":"neotest_go","Test":"TestAddOne","Elapsed":0}
{"Time":"2024-05-25T17:06:16.986996509+09:00","Action":"run","Package":"neotest_go","Test":"TestAddTwo"}
{"Time":"2024-05-25T17:06:16.986999108+09:00","Action":"output","Package":"neotest_go","Test":"TestAddTwo","Output":"=== RUN TestAddTwo\n"}
{"Time":"2024-05-25T17:06:16.98700243+09:00","Action":"output","Package":"neotest_go","Test":"TestAddTwo","Output":"--- PASS: TestAddTwo (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987005287+09:00","Action":"pass","Package":"neotest_go","Test":"TestAddTwo","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987008349+09:00","Action":"run","Package":"neotest_go","Test":"TestSomeTest"}
{"Time":"2024-05-25T17:06:16.987011032+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest","Output":"=== RUN TestSomeTest\n"}
{"Time":"2024-05-25T17:06:16.987014678+09:00","Action":"run","Package":"neotest_go","Test":"TestSomeTest/AccessDenied1"}
{"Time":"2024-05-25T17:06:16.987017664+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied1","Output":"=== RUN TestSomeTest/AccessDenied1\n"}
{"Time":"2024-05-25T17:06:16.98702332+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied1","Output":"AccessDenied1 GET /api/nothing lalala 403\n"}
{"Time":"2024-05-25T17:06:16.987030097+09:00","Action":"run","Package":"neotest_go","Test":"TestSomeTest/AccessDenied2"}
{"Time":"2024-05-25T17:06:16.9870326+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied2","Output":"=== RUN TestSomeTest/AccessDenied2\n"}
{"Time":"2024-05-25T17:06:16.987035243+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied2","Output":"AccessDenied2 GET /api/nothing lalala 403\n"}
{"Time":"2024-05-25T17:06:16.98703803+09:00","Action":"run","Package":"neotest_go","Test":"TestSomeTest/AccessDenied3"}
{"Time":"2024-05-25T17:06:16.987040299+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied3","Output":"=== RUN TestSomeTest/AccessDenied3\n"}
{"Time":"2024-05-25T17:06:16.987042979+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied3","Output":"AccessDenied3 GET /api/nothing lalala 403\n"}
{"Time":"2024-05-25T17:06:16.987045694+09:00","Action":"run","Package":"neotest_go","Test":"TestSomeTest/AccessDenied4"}
{"Time":"2024-05-25T17:06:16.987048493+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied4","Output":"=== RUN TestSomeTest/AccessDenied4\n"}
{"Time":"2024-05-25T17:06:16.987051059+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied4","Output":"AccessDenied4 GET /api/nothing lalala 403\n"}
{"Time":"2024-05-25T17:06:16.987053703+09:00","Action":"run","Package":"neotest_go","Test":"TestSomeTest/AccessDenied5"}
{"Time":"2024-05-25T17:06:16.987055897+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied5","Output":"=== RUN TestSomeTest/AccessDenied5\n"}
{"Time":"2024-05-25T17:06:16.987058733+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied5","Output":"AccessDenied5 GET /api/nothing lalala 403\n"}
{"Time":"2024-05-25T17:06:16.987062114+09:00","Action":"run","Package":"neotest_go","Test":"TestSomeTest/AccessDenied6"}
{"Time":"2024-05-25T17:06:16.987064491+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied6","Output":"=== RUN TestSomeTest/AccessDenied6\n"}
{"Time":"2024-05-25T17:06:16.987067709+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied6","Output":"AccessDenied6 GET /api/nothing lalala 403\n"}
{"Time":"2024-05-25T17:06:16.987076359+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest","Output":"--- PASS: TestSomeTest (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987079722+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied1","Output":" --- PASS: TestSomeTest/AccessDenied1 (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987082855+09:00","Action":"pass","Package":"neotest_go","Test":"TestSomeTest/AccessDenied1","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987085329+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied2","Output":" --- PASS: TestSomeTest/AccessDenied2 (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987088221+09:00","Action":"pass","Package":"neotest_go","Test":"TestSomeTest/AccessDenied2","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987090756+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied3","Output":" --- PASS: TestSomeTest/AccessDenied3 (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987094115+09:00","Action":"pass","Package":"neotest_go","Test":"TestSomeTest/AccessDenied3","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987096613+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied4","Output":" --- PASS: TestSomeTest/AccessDenied4 (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987099588+09:00","Action":"pass","Package":"neotest_go","Test":"TestSomeTest/AccessDenied4","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987104022+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied5","Output":" --- PASS: TestSomeTest/AccessDenied5 (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987107814+09:00","Action":"pass","Package":"neotest_go","Test":"TestSomeTest/AccessDenied5","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987110865+09:00","Action":"output","Package":"neotest_go","Test":"TestSomeTest/AccessDenied6","Output":" --- PASS: TestSomeTest/AccessDenied6 (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987113695+09:00","Action":"pass","Package":"neotest_go","Test":"TestSomeTest/AccessDenied6","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987116401+09:00","Action":"pass","Package":"neotest_go","Test":"TestSomeTest","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987119142+09:00","Action":"run","Package":"neotest_go","Test":"TestSplit"}
{"Time":"2024-05-25T17:06:16.987121813+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit","Output":"=== RUN TestSplit\n"}
{"Time":"2024-05-25T17:06:16.987124271+09:00","Action":"run","Package":"neotest_go","Test":"TestSplit/simple"}
{"Time":"2024-05-25T17:06:16.987126899+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit/simple","Output":"=== RUN TestSplit/simple\n"}
{"Time":"2024-05-25T17:06:16.987129965+09:00","Action":"run","Package":"neotest_go","Test":"TestSplit/wrong_sep"}
{"Time":"2024-05-25T17:06:16.987132221+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit/wrong_sep","Output":"=== RUN TestSplit/wrong_sep\n"}
{"Time":"2024-05-25T17:06:16.98713529+09:00","Action":"run","Package":"neotest_go","Test":"TestSplit/no_sep"}
{"Time":"2024-05-25T17:06:16.987138035+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit/no_sep","Output":"=== RUN TestSplit/no_sep\n"}
{"Time":"2024-05-25T17:06:16.987140671+09:00","Action":"run","Package":"neotest_go","Test":"TestSplit/trailing_sep"}
{"Time":"2024-05-25T17:06:16.987143812+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit/trailing_sep","Output":"=== RUN TestSplit/trailing_sep\n"}
{"Time":"2024-05-25T17:06:16.987148473+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit/trailing_sep","Output":" map_table_test.go:25: trailing sep: expected: [a b c], got: [a b c ]\n"}
{"Time":"2024-05-25T17:06:16.987152641+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit","Output":"--- FAIL: TestSplit (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987156528+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit/simple","Output":" --- PASS: TestSplit/simple (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.98716009+09:00","Action":"pass","Package":"neotest_go","Test":"TestSplit/simple","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987163018+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit/wrong_sep","Output":" --- PASS: TestSplit/wrong_sep (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987166426+09:00","Action":"pass","Package":"neotest_go","Test":"TestSplit/wrong_sep","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987169756+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit/no_sep","Output":" --- PASS: TestSplit/no_sep (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987173446+09:00","Action":"pass","Package":"neotest_go","Test":"TestSplit/no_sep","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987176509+09:00","Action":"output","Package":"neotest_go","Test":"TestSplit/trailing_sep","Output":" --- FAIL: TestSplit/trailing_sep (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987179658+09:00","Action":"fail","Package":"neotest_go","Test":"TestSplit/trailing_sep","Elapsed":0}
{"Time":"2024-05-25T17:06:16.9871823+09:00","Action":"fail","Package":"neotest_go","Test":"TestSplit","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987185013+09:00","Action":"run","Package":"neotest_go","Test":"TestExampleTestSuite"}
{"Time":"2024-05-25T17:06:16.987187634+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite","Output":"=== RUN TestExampleTestSuite\n"}
{"Time":"2024-05-25T17:06:16.987358969+09:00","Action":"run","Package":"neotest_go","Test":"TestExampleTestSuite/TestExample"}
{"Time":"2024-05-25T17:06:16.987372775+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExample","Output":"=== RUN TestExampleTestSuite/TestExample\n"}
{"Time":"2024-05-25T17:06:16.987378537+09:00","Action":"run","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure"}
{"Time":"2024-05-25T17:06:16.987383079+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure","Output":"=== RUN TestExampleTestSuite/TestExampleFailure\n"}
{"Time":"2024-05-25T17:06:16.987611537+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure","Output":" suite_test.go:32: \n"}
{"Time":"2024-05-25T17:06:16.987626951+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure","Output":" \tError Trace:\tsuite_test.go:32\n"}
{"Time":"2024-05-25T17:06:16.987631582+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure","Output":" \tError: \tNot equal: \n"}
{"Time":"2024-05-25T17:06:16.987635334+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure","Output":" \t \texpected: 5\n"}
{"Time":"2024-05-25T17:06:16.9876384+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure","Output":" \t \tactual : 3\n"}
{"Time":"2024-05-25T17:06:16.987641353+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure","Output":" \tTest: \tTestExampleTestSuite/TestExampleFailure\n"}
{"Time":"2024-05-25T17:06:16.987649622+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite","Output":"--- FAIL: TestExampleTestSuite (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987655287+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExample","Output":" --- PASS: TestExampleTestSuite/TestExample (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987659094+09:00","Action":"pass","Package":"neotest_go","Test":"TestExampleTestSuite/TestExample","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987663126+09:00","Action":"output","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure","Output":" --- FAIL: TestExampleTestSuite/TestExampleFailure (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987666501+09:00","Action":"fail","Package":"neotest_go","Test":"TestExampleTestSuite/TestExampleFailure","Elapsed":0}
{"Time":"2024-05-25T17:06:16.9876691+09:00","Action":"fail","Package":"neotest_go","Test":"TestExampleTestSuite","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987671645+09:00","Action":"run","Package":"neotest_go","Test":"TestOdd"}
{"Time":"2024-05-25T17:06:16.987674132+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd","Output":"=== RUN TestOdd\n"}
{"Time":"2024-05-25T17:06:16.987676921+09:00","Action":"run","Package":"neotest_go","Test":"TestOdd/odd"}
{"Time":"2024-05-25T17:06:16.987680637+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd/odd","Output":"=== RUN TestOdd/odd\n"}
{"Time":"2024-05-25T17:06:16.987683855+09:00","Action":"run","Package":"neotest_go","Test":"TestOdd/odd/5_is_odd"}
{"Time":"2024-05-25T17:06:16.98769839+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd/odd/5_is_odd","Output":"=== RUN TestOdd/odd/5_is_odd\n"}
{"Time":"2024-05-25T17:06:16.987711715+09:00","Action":"run","Package":"neotest_go","Test":"TestOdd/odd/5_is_odd/9_is_odd"}
{"Time":"2024-05-25T17:06:16.987713743+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd/odd/5_is_odd/9_is_odd","Output":"=== RUN TestOdd/odd/5_is_odd/9_is_odd\n"}
{"Time":"2024-05-25T17:06:16.987715909+09:00","Action":"run","Package":"neotest_go","Test":"TestOdd/odd/7_is_odd"}
{"Time":"2024-05-25T17:06:16.987717512+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd/odd/7_is_odd","Output":"=== RUN TestOdd/odd/7_is_odd\n"}
{"Time":"2024-05-25T17:06:16.987720928+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd","Output":"--- PASS: TestOdd (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987723417+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd/odd","Output":" --- PASS: TestOdd/odd (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987726445+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd/odd/5_is_odd","Output":" --- PASS: TestOdd/odd/5_is_odd (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987728552+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd/odd/5_is_odd/9_is_odd","Output":" --- PASS: TestOdd/odd/5_is_odd/9_is_odd (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.98773067+09:00","Action":"pass","Package":"neotest_go","Test":"TestOdd/odd/5_is_odd/9_is_odd","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987733354+09:00","Action":"pass","Package":"neotest_go","Test":"TestOdd/odd/5_is_odd","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987734868+09:00","Action":"output","Package":"neotest_go","Test":"TestOdd/odd/7_is_odd","Output":" --- PASS: TestOdd/odd/7_is_odd (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987736884+09:00","Action":"pass","Package":"neotest_go","Test":"TestOdd/odd/7_is_odd","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987738363+09:00","Action":"pass","Package":"neotest_go","Test":"TestOdd/odd","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987739718+09:00","Action":"pass","Package":"neotest_go","Test":"TestOdd","Elapsed":0}
{"Time":"2024-05-25T17:06:16.987741167+09:00","Action":"run","Package":"neotest_go","Test":"Example_hello_ok"}
{"Time":"2024-05-25T17:06:16.987742665+09:00","Action":"output","Package":"neotest_go","Test":"Example_hello_ok","Output":"=== RUN Example_hello_ok\n"}
{"Time":"2024-05-25T17:06:16.987748658+09:00","Action":"output","Package":"neotest_go","Test":"Example_hello_ok","Output":"--- PASS: Example_hello_ok (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987750589+09:00","Action":"pass","Package":"neotest_go","Test":"Example_hello_ok","Elapsed":0}
{"Time":"2024-05-25T17:06:16.98775208+09:00","Action":"run","Package":"neotest_go","Test":"Example_hello_ng"}
{"Time":"2024-05-25T17:06:16.987753501+09:00","Action":"output","Package":"neotest_go","Test":"Example_hello_ng","Output":"=== RUN Example_hello_ng\n"}
{"Time":"2024-05-25T17:06:16.987755588+09:00","Action":"output","Package":"neotest_go","Test":"Example_hello_ng","Output":"--- FAIL: Example_hello_ng (0.00s)\n"}
{"Time":"2024-05-25T17:06:16.987757621+09:00","Action":"output","Package":"neotest_go","Test":"Example_hello_ng","Output":"got:\n"}
{"Time":"2024-05-25T17:06:16.987759383+09:00","Action":"output","Package":"neotest_go","Test":"Example_hello_ng","Output":"hello world\n"}
{"Time":"2024-05-25T17:06:16.987761201+09:00","Action":"output","Package":"neotest_go","Test":"Example_hello_ng","Output":"want:\n"}
{"Time":"2024-05-25T17:06:16.987762945+09:00","Action":"output","Package":"neotest_go","Test":"Example_hello_ng","Output":"NG pattern\n"}
{"Time":"2024-05-25T17:06:16.987764982+09:00","Action":"fail","Package":"neotest_go","Test":"Example_hello_ng","Elapsed":0}
{"Time":"2024-05-25T17:06:16.988651539+09:00","Action":"output","Package":"neotest_go","Output":"FAIL\n"}
{"Time":"2024-05-25T17:06:16.988706481+09:00","Action":"output","Package":"neotest_go","Output":"FAIL\tneotest_go\t0.004s\n"}
{"Time":"2024-05-25T17:06:16.988710395+09:00","Action":"fail","Package":"neotest_go","Elapsed":0.004}