From b87fb9e6815f5d482b1ab575c2f652e5160cf61c Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Tue, 30 Jan 2024 01:13:23 +0800 Subject: [PATCH 1/8] refactor: unify test language for tests --- error_template_test.go | 43 ++++------------------------------ source_test.go | 50 +++------------------------------------- testlang.go | 52 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 85 deletions(-) create mode 100644 testlang.go diff --git a/error_template_test.go b/error_template_test.go index 2ab23e6..992e49e 100644 --- a/error_template_test.go +++ b/error_template_test.go @@ -1,51 +1,18 @@ package errgoengine_test import ( - "context" "testing" lib "github.com/nedpals/errgoengine" testutils "github.com/nedpals/errgoengine/test_utils" ) -type TestAnalyzer struct{} - -func (TestAnalyzer) FallbackSymbol() lib.Symbol { - return nil -} - -func (TestAnalyzer) FindSymbol(string) lib.Symbol { return nil } - -func (TestAnalyzer) AnalyzeNode(context.Context, lib.SyntaxNode) lib.Symbol { - return nil -} - -func (TestAnalyzer) AnalyzeImport(lib.ImportParams) lib.ResolvedImport { - return lib.ResolvedImport{} -} - -var testLanguage = &lib.Language{ - Name: "TestLang", - FilePatterns: []string{".test"}, - StackTracePattern: `\sin (?P\S+) at (?P\S+):(?P\d+)`, - LocationConverter: func(ctx lib.LocationConverterContext) lib.Location { - return lib.Location{ - DocumentPath: ctx.Path, - StartPos: lib.Position{0, 0, 0}, - EndPos: lib.Position{0, 0, 0}, - } - }, - AnalyzerFactory: func(cd *lib.ContextData) lib.LanguageAnalyzer { - return TestAnalyzer{} - }, -} - func emptyExplainFn(cd *lib.ContextData, gen *lib.ExplainGenerator) {} func emptyBugFixFn(cd *lib.ContextData, gen *lib.BugFixGenerator) {} func setupTemplate(template lib.ErrorTemplate) (*lib.CompiledErrorTemplate, error) { errorTemplates := lib.ErrorTemplates{} - return errorTemplates.Add(testLanguage, template) + return errorTemplates.Add(lib.TestLanguage, template) } func TestErrorTemplate(t *testing.T) { @@ -62,7 +29,7 @@ func TestErrorTemplate(t *testing.T) { } testutils.Equals(t, tmp.Name, "SampleError") - testutils.Equals(t, tmp.Language, testLanguage) + testutils.Equals(t, tmp.Language, lib.TestLanguage) testutils.Equals(t, tmp.Pattern.String(), `(?m)^This is a sample error(?P(?:.|\s)*)$`) testutils.Equals(t, tmp.StackTraceRegex().String(), `(?m)\sin (?P\S+) at (?P\S+):(?P\d+)`) testutils.ExpectNil(t, tmp.StackTracePattern) @@ -82,7 +49,7 @@ func TestErrorTemplate(t *testing.T) { } testutils.Equals(t, tmp.Name, "SampleError2") - testutils.Equals(t, tmp.Language, testLanguage) + testutils.Equals(t, tmp.Language, lib.TestLanguage) testutils.Equals(t, tmp.Pattern.String(), `(?m)^This is a sample error with stack trace(?P(?:.|\s)*)$`) testutils.Equals(t, tmp.StackTraceRegex().String(), `(?P\S+):(?P\S+):(?P\d+)`) }) @@ -100,7 +67,7 @@ func TestErrorTemplate(t *testing.T) { } testutils.Equals(t, tmp.Name, "SampleError3") - testutils.Equals(t, tmp.Language, testLanguage) + testutils.Equals(t, tmp.Language, lib.TestLanguage) testutils.Equals(t, tmp.Pattern.String(), `(?m)^Stack trace in middle (?P(?:.|\s)*)test$`) testutils.ExpectNil(t, tmp.StackTracePattern) }) @@ -118,7 +85,7 @@ func TestStackTraceRegex(t *testing.T) { t.Fatal(err) } - testutils.Equals(t, tmp.StackTraceRegex().String(), "(?m)"+testLanguage.StackTracePattern) + testutils.Equals(t, tmp.StackTraceRegex().String(), "(?m)"+lib.TestLanguage.StackTracePattern) }) t.Run("With custom stack trace", func(t *testing.T) { diff --git a/source_test.go b/source_test.go index 78496ef..45fa3b7 100644 --- a/source_test.go +++ b/source_test.go @@ -1,60 +1,16 @@ package errgoengine import ( - "context" "strings" "testing" sitter "github.com/smacker/go-tree-sitter" - "github.com/smacker/go-tree-sitter/python" ) -var testLanguage = &Language{ - Name: "Python", - FilePatterns: []string{".py"}, - SitterLanguage: python.GetLanguage(), - StackTracePattern: `\s+File "(?P\S+)", line (?P\d+), in (?P\S+)`, - ErrorPattern: `Traceback \(most recent call last\):$stacktrace$message`, - AnalyzerFactory: func(cd *ContextData) LanguageAnalyzer { - return &testAnalyzer{cd} - }, - SymbolsToCapture: ` -(expression_statement - (assignment - left: (identifier) @assignment.name - right: (identifier) @assignment.content) @assignment) -`, -} - -type testAnalyzer struct { - *ContextData -} - -func (an *testAnalyzer) FallbackSymbol() Symbol { - return Builtin("any") -} - -func (an *testAnalyzer) FindSymbol(name string) Symbol { - return nil -} - -func (an *testAnalyzer) AnalyzeNode(_ context.Context, n SyntaxNode) Symbol { - // TODO: - return Builtin("void") -} - -func (an *testAnalyzer) AnalyzeImport(params ImportParams) ResolvedImport { - // TODO: - - return ResolvedImport{ - Path: "", - } -} - func TestParseDocument(t *testing.T) { parser := sitter.NewParser() - doc, err := ParseDocument("test", strings.NewReader(`hello = 1`), parser, testLanguage, nil) + doc, err := ParseDocument("test", strings.NewReader(`hello = 1`), parser, TestLanguage, nil) if err != nil { t.Error(err) } @@ -67,7 +23,7 @@ func TestParseDocument(t *testing.T) { func TestEditableDocument(t *testing.T) { parser := sitter.NewParser() - doc, err := ParseDocument("test", strings.NewReader(`hello = 1`), parser, testLanguage, nil) + doc, err := ParseDocument("test", strings.NewReader(`hello = 1`), parser, TestLanguage, nil) if err != nil { t.Error(err) } else if doc.TotalLines() < 1 { @@ -371,7 +327,7 @@ func TestEditableDocument(t *testing.T) { }) t.Run("EditableDocument.ReplaceWithPadding", func(t *testing.T) { - doc, err := ParseDocument("test", strings.NewReader(` hello = 1`), parser, testLanguage, nil) + doc, err := ParseDocument("test", strings.NewReader(` hello = 1`), parser, TestLanguage, nil) if err != nil { t.Error(err) } else if doc.TotalLines() < 1 { diff --git a/testlang.go b/testlang.go new file mode 100644 index 0000000..14a225b --- /dev/null +++ b/testlang.go @@ -0,0 +1,52 @@ +package errgoengine + +import ( + "context" +) + +var TestLanguage = &Language{ + Name: "TestLang", + FilePatterns: []string{".test"}, + StackTracePattern: `\sin (?P\S+) at (?P\S+):(?P\d+)`, + LocationConverter: func(ctx LocationConverterContext) Location { + return Location{ + DocumentPath: ctx.Path, + StartPos: Position{Line: 0, Column: 0, Index: 0}, + EndPos: Position{Line: 0, Column: 0, Index: 0}, + } + }, + AnalyzerFactory: func(cd *ContextData) LanguageAnalyzer { + return &testAnalyzer{} + }, + SymbolsToCapture: ` +(expression_statement + (assignment + left: (identifier) @assignment.name + right: (identifier) @assignment.content) @assignment) + `, +} + +type testAnalyzer struct { + *ContextData +} + +func (an *testAnalyzer) FallbackSymbol() Symbol { + return Builtin("any") +} + +func (an *testAnalyzer) FindSymbol(name string) Symbol { + return nil +} + +func (an *testAnalyzer) AnalyzeNode(_ context.Context, n SyntaxNode) Symbol { + // TODO: + return Builtin("void") +} + +func (an *testAnalyzer) AnalyzeImport(params ImportParams) ResolvedImport { + // TODO: + + return ResolvedImport{ + Path: "", + } +} From 05375fae1e3d2f57d084b7dbc88cd2da6465c135 Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Tue, 30 Jan 2024 09:49:46 +0800 Subject: [PATCH 2/8] feat: extract code from errgoengine.go, add more tests --- context.go | 14 +++ errgoengine.go | 48 +------- error_template.go | 56 ++++++++++ error_template_test.go | 243 ++++++++++++++++++++++++++++++++++++++++- language.go | 3 + testlang.go | 7 -- 6 files changed, 313 insertions(+), 58 deletions(-) diff --git a/context.go b/context.go index 6a82432..e055602 100644 --- a/context.go +++ b/context.go @@ -53,3 +53,17 @@ func (data *ContextData) AddVariable(name string, value string) { data.Variables[name] = value } + +func (data *ContextData) AddVariables(vars map[string]string) { + if len(vars) == 0 { + return + } + + if data.Variables == nil { + data.Variables = make(map[string]string) + } + + for k, v := range vars { + data.Variables[k] = v + } +} diff --git a/errgoengine.go b/errgoengine.go index 22e2440..8c2cd9c 100644 --- a/errgoengine.go +++ b/errgoengine.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "io/fs" - "path/filepath" sitter "github.com/smacker/go-tree-sitter" ) @@ -58,52 +57,11 @@ func (e *ErrgoEngine) Analyze(workingPath, msg string) (*CompiledErrorTemplate, contextData.Analyzer = template.Language.AnalyzerFactory(contextData) contextData.AddVariable("message", msg) - groupNames := template.Pattern.SubexpNames() - for _, submatches := range template.Pattern.FindAllStringSubmatch(msg, -1) { - for idx, matchedContent := range submatches { - if len(groupNames[idx]) == 0 { - continue - } - - contextData.AddVariable(groupNames[idx], matchedContent) - } - } + // extract variables from the error message + contextData.AddVariables(template.ExtractVariables(msg)) // extract stack trace - rawStackTraceItem := contextData.Variables["stacktrace"] - symbolGroupIdx := template.StackTraceRegex().SubexpIndex("symbol") - pathGroupIdx := template.StackTraceRegex().SubexpIndex("path") - posGroupIdx := template.StackTraceRegex().SubexpIndex("position") - stackTraceMatches := template.StackTraceRegex().FindAllStringSubmatch(rawStackTraceItem, -1) - - for _, submatches := range stackTraceMatches { - if len(submatches) == 0 { - continue - } - - rawSymbolName := "" - if symbolGroupIdx != -1 { - rawSymbolName = submatches[symbolGroupIdx] - } - rawPath := submatches[pathGroupIdx] - rawPos := submatches[posGroupIdx] - - // convert relative paths to absolute for parsing - if len(workingPath) != 0 && !filepath.IsAbs(rawPath) { - rawPath = filepath.Clean(filepath.Join(workingPath, rawPath)) - } - - stLoc := template.Language.LocationConverter(LocationConverterContext{ - Path: rawPath, - Pos: rawPos, - ContextData: contextData, - }) - if contextData.TraceStack == nil { - contextData.TraceStack = TraceStack{} - } - - contextData.TraceStack.Add(rawSymbolName, stLoc) - } + contextData.TraceStack = template.ExtractStackTrace(contextData) // open contents of the extracted stack file locations parser := sitter.NewParser() diff --git a/error_template.go b/error_template.go index 1eef406..b28bd6b 100644 --- a/error_template.go +++ b/error_template.go @@ -2,6 +2,7 @@ package errgoengine import ( "fmt" + "path/filepath" "regexp" "strings" ) @@ -37,6 +38,61 @@ func (tmp *CompiledErrorTemplate) StackTraceRegex() *regexp.Regexp { return tmp.Language.stackTraceRegex } +func (tmp *CompiledErrorTemplate) ExtractVariables(msg string) map[string]string { + variables := map[string]string{} + groupNames := tmp.Pattern.SubexpNames() + for _, submatches := range tmp.Pattern.FindAllStringSubmatch(msg, -1) { + for idx, matchedContent := range submatches { + if len(groupNames[idx]) == 0 { + continue + } + + variables[groupNames[idx]] = matchedContent + } + } + return variables +} + +func (tmp *CompiledErrorTemplate) ExtractStackTrace(cd *ContextData) TraceStack { + traceStack := TraceStack{} + workingPath := cd.WorkingPath + + rawStackTraceItem := cd.Variables["stacktrace"] + stackTraceRegex := tmp.StackTraceRegex() + symbolGroupIdx := stackTraceRegex.SubexpIndex("symbol") + pathGroupIdx := stackTraceRegex.SubexpIndex("path") + posGroupIdx := stackTraceRegex.SubexpIndex("position") + stackTraceMatches := stackTraceRegex.FindAllStringSubmatch(rawStackTraceItem, -1) + + for _, submatches := range stackTraceMatches { + if len(submatches) == 0 { + continue + } + + rawSymbolName := "" + if symbolGroupIdx != -1 { + rawSymbolName = submatches[symbolGroupIdx] + } + rawPath := submatches[pathGroupIdx] + rawPos := submatches[posGroupIdx] + + // convert relative paths to absolute for parsing + if len(workingPath) != 0 && !filepath.IsAbs(rawPath) { + rawPath = filepath.Clean(filepath.Join(workingPath, rawPath)) + } + + stLoc := tmp.Language.LocationConverter(LocationConverterContext{ + Path: rawPath, + Pos: rawPos, + ContextData: cd, + }) + + traceStack.Add(rawSymbolName, stLoc) + } + + return traceStack +} + type ErrorTemplates map[string]*CompiledErrorTemplate const defaultStackTraceRegex = `(?P(?:.|\s)*)` diff --git a/error_template_test.go b/error_template_test.go index 992e49e..89de903 100644 --- a/error_template_test.go +++ b/error_template_test.go @@ -1,6 +1,9 @@ package errgoengine_test import ( + "fmt" + "reflect" + "strings" "testing" lib "github.com/nedpals/errgoengine" @@ -18,7 +21,7 @@ func setupTemplate(template lib.ErrorTemplate) (*lib.CompiledErrorTemplate, erro func TestErrorTemplate(t *testing.T) { t.Run("Simple", func(t *testing.T) { tmp, err := setupTemplate(lib.ErrorTemplate{ - Name: "SampleError", + Name: "ErrorA", Pattern: "This is a sample error", OnGenExplainFn: emptyExplainFn, OnGenBugFixFn: emptyBugFixFn, @@ -28,7 +31,7 @@ func TestErrorTemplate(t *testing.T) { t.Fatal(err) } - testutils.Equals(t, tmp.Name, "SampleError") + testutils.Equals(t, tmp.Name, "ErrorA") testutils.Equals(t, tmp.Language, lib.TestLanguage) testutils.Equals(t, tmp.Pattern.String(), `(?m)^This is a sample error(?P(?:.|\s)*)$`) testutils.Equals(t, tmp.StackTraceRegex().String(), `(?m)\sin (?P\S+) at (?P\S+):(?P\d+)`) @@ -37,7 +40,7 @@ func TestErrorTemplate(t *testing.T) { t.Run("With custom stack trace", func(t *testing.T) { tmp, err := setupTemplate(lib.ErrorTemplate{ - Name: "SampleError2", + Name: "ErrorB", Pattern: "This is a sample error with stack trace", OnGenExplainFn: emptyExplainFn, OnGenBugFixFn: emptyBugFixFn, @@ -48,7 +51,7 @@ func TestErrorTemplate(t *testing.T) { t.Fatal(err) } - testutils.Equals(t, tmp.Name, "SampleError2") + testutils.Equals(t, tmp.Name, "ErrorB") testutils.Equals(t, tmp.Language, lib.TestLanguage) testutils.Equals(t, tmp.Pattern.String(), `(?m)^This is a sample error with stack trace(?P(?:.|\s)*)$`) testutils.Equals(t, tmp.StackTraceRegex().String(), `(?P\S+):(?P\S+):(?P\d+)`) @@ -56,7 +59,7 @@ func TestErrorTemplate(t *testing.T) { t.Run("With custom error pattern", func(t *testing.T) { tmp, err := setupTemplate(lib.ErrorTemplate{ - Name: "SampleError3", + Name: "ErrorC", Pattern: lib.CustomErrorPattern("Stack trace in middle $stacktracetest"), OnGenExplainFn: emptyExplainFn, OnGenBugFixFn: emptyBugFixFn, @@ -66,7 +69,7 @@ func TestErrorTemplate(t *testing.T) { t.Fatal(err) } - testutils.Equals(t, tmp.Name, "SampleError3") + testutils.Equals(t, tmp.Name, "ErrorC") testutils.Equals(t, tmp.Language, lib.TestLanguage) testutils.Equals(t, tmp.Pattern.String(), `(?m)^Stack trace in middle (?P(?:.|\s)*)test$`) testutils.ExpectNil(t, tmp.StackTracePattern) @@ -133,4 +136,232 @@ func TestStackTraceRegex(t *testing.T) { t.Fatal("expected panic, got successful execution instead") }) + + t.Run("Extract variables", func(t *testing.T) { + tmp, err := setupTemplate(lib.ErrorTemplate{ + Name: "A", + Pattern: "invalid input '(?P.*)'", + OnGenExplainFn: emptyExplainFn, + OnGenBugFixFn: emptyBugFixFn, + }) + if err != nil { + t.Fatal(err) + } + + variables := tmp.ExtractVariables("invalid input '123abc'\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1") + exp := map[string]string{ + "stacktrace": "\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", + "input": "123abc", + } + + if !reflect.DeepEqual(variables, exp) { + t.Fatalf("expected %v, got %v", exp, variables) + } + }) + + t.Run("Extract variables no stack trace", func(t *testing.T) { + tmp, err := setupTemplate(lib.ErrorTemplate{ + Name: "A", + Pattern: "invalid input '(?P.*)'", + OnGenExplainFn: emptyExplainFn, + OnGenBugFixFn: emptyBugFixFn, + }) + if err != nil { + t.Fatal(err) + } + + variables := tmp.ExtractVariables("invalid input '123abc'") + exp := map[string]string{ + "input": "123abc", + "stacktrace": "", + } + + if !reflect.DeepEqual(variables, exp) { + t.Fatalf("expected %v, got %v", exp, variables) + } + }) + + t.Run("Extract variables no variables", func(t *testing.T) { + tmp, err := setupTemplate(lib.ErrorTemplate{ + Name: "A", + Pattern: "invalid input '123abc'", + OnGenExplainFn: emptyExplainFn, + OnGenBugFixFn: emptyBugFixFn, + }) + if err != nil { + t.Fatal(err) + } + + variables := tmp.ExtractVariables("invalid input '123abc'") + exp := map[string]string{ + "stacktrace": "", + } + + if !reflect.DeepEqual(variables, exp) { + t.Fatalf("expected %v, got %v", exp, variables) + } + }) + + t.Run("Extract stack trace", func(t *testing.T) { + tmp, err := setupTemplate(lib.ErrorTemplate{ + Name: "A", + Pattern: "invalid input '(?P.*)'", + OnGenExplainFn: emptyExplainFn, + OnGenBugFixFn: emptyBugFixFn, + }) + if err != nil { + t.Fatal(err) + } + + cd := lib.NewContextData(lib.NewEmptyStore(), "/home/user") + cd.Variables = map[string]string{ + "stacktrace": "\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", + } + + stackTrace := tmp.ExtractStackTrace(cd) + exp := lib.TraceStack{ + lib.StackTraceEntry{ + SymbolName: "main", + Location: lib.Location{ + DocumentPath: "/home/user/main.py", + StartPos: lib.Position{ + Line: 123, + Column: 0, + Index: 0, + }, + EndPos: lib.Position{ + Line: 123, + Column: 0, + Index: 0, + }, + }, + }, + lib.StackTraceEntry{ + SymbolName: "main", + Location: lib.Location{ + DocumentPath: "/home/user/main.py", + StartPos: lib.Position{ + Line: 1, + Column: 0, + Index: 0, + }, + EndPos: lib.Position{ + Line: 1, + Column: 0, + Index: 0, + }, + }, + }, + } + + if !reflect.DeepEqual(stackTrace, exp) { + t.Fatalf("expected %v, got %v", exp, stackTrace) + } + }) +} + +func TestErrorTemplates(t *testing.T) { + errorTemplates := lib.ErrorTemplates{} + tmp := errorTemplates.MustAdd(lib.TestLanguage, lib.ErrorTemplate{ + Name: "ErrorA", + Pattern: `This is a sample error\n`, + OnGenExplainFn: emptyExplainFn, + OnGenBugFixFn: emptyBugFixFn, + }) + + tmp2 := errorTemplates.MustAdd(lib.TestLanguage, lib.ErrorTemplate{ + Name: "ErrorB", + Pattern: `Another exmaple error\n`, + OnGenExplainFn: emptyExplainFn, + OnGenBugFixFn: emptyBugFixFn, + }) + + fmt.Println(tmp.Pattern.String()) + fmt.Println(tmp2.Pattern.String()) + + t.Run("Simple", func(t *testing.T) { + inputs := []string{ + "This is a sample error", + "Another exmaple error", + } + + expected := []*lib.CompiledErrorTemplate{ + tmp, + tmp2, + } + + for i, input := range inputs { + matched := errorTemplates.Match(input + "\n" + lib.TestLanguage.StackTracePattern) + + if !reflect.DeepEqual(matched, expected[i]) { + t.Fatalf("expected %s, got %s", expected[i].Name, matched.Name) + } + } + }) + + t.Run("SimpleReverse", func(t *testing.T) { + inputs := []string{ + "Another exmaple error", + "This is a sample error", + } + + expected := []*lib.CompiledErrorTemplate{ + tmp2, + tmp, + } + + for i, input := range inputs { + matched := errorTemplates.Match(input + "\n" + lib.TestLanguage.StackTracePattern) + + if !reflect.DeepEqual(matched, expected[i]) { + t.Fatalf("expected %s, got %s", expected[i].Name, matched.Name) + } + } + }) + + t.Run("Should be nil", func(t *testing.T) { + inputs := []string{ + "This is a sample errorz\n", + "AAnother exmaple error\n", + "Another eaaxmaple error\n" + lib.TestLanguage.StackTracePattern, + "This is a sample erroar\n" + lib.TestLanguage.StackTracePattern, + } + + for _, input := range inputs { + matched := errorTemplates.Match(input) + + if matched != nil { + t.Fatalf("expected nil, got %s", matched.Name) + } + } + }) + + t.Run("Stacked", func(t *testing.T) { + inputs := []string{ + "This is a sample error", + "Another exmaple error", + } + + input := strings.Join(inputs, "\nin main at /home/user/main.py:1\n\n") + matched := errorTemplates.Match(input) + + if !reflect.DeepEqual(matched, tmp) { + t.Fatalf("expected %s, got %s", tmp.Name, matched.Name) + } + }) + + t.Run("StackedReverse", func(t *testing.T) { + // reverse + inputs := []string{ + "Another exmaple error", + "This is a sample error", + } + + input := strings.Join(inputs, "\nin main at /home/user/main.py:1\n\n") + matched := errorTemplates.Match(input) + + if !reflect.DeepEqual(matched, tmp2) { + t.Fatalf("expected %s, got %s", tmp2.Name, matched.Name) + } + }) } diff --git a/language.go b/language.go index 81cf193..eba9b1d 100644 --- a/language.go +++ b/language.go @@ -28,6 +28,8 @@ type LocationConverterContext struct { ContextData *ContextData } +type LocationConverterFunc func(ctx LocationConverterContext) Position + type Language struct { isCompiled bool stackTraceRegex *regexp.Regexp @@ -126,6 +128,7 @@ func DefaultLocationConverter(ctx LocationConverterContext) Location { if _, err := fmt.Sscanf(ctx.Pos, "%d", &trueLine); err != nil { panic(err) } + return Location{ DocumentPath: ctx.Path, StartPos: Position{Line: trueLine}, diff --git a/testlang.go b/testlang.go index 14a225b..07cf02d 100644 --- a/testlang.go +++ b/testlang.go @@ -8,13 +8,6 @@ var TestLanguage = &Language{ Name: "TestLang", FilePatterns: []string{".test"}, StackTracePattern: `\sin (?P\S+) at (?P\S+):(?P\d+)`, - LocationConverter: func(ctx LocationConverterContext) Location { - return Location{ - DocumentPath: ctx.Path, - StartPos: Position{Line: 0, Column: 0, Index: 0}, - EndPos: Position{Line: 0, Column: 0, Index: 0}, - } - }, AnalyzerFactory: func(cd *ContextData) LanguageAnalyzer { return &testAnalyzer{} }, From f4371c856fcc3ee832d574a91cbff7e410079f7e Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Tue, 30 Jan 2024 10:42:05 +0800 Subject: [PATCH 3/8] fix: separate test function for extract{variables|stacktrace} --- error_template_test.go | 68 ++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/error_template_test.go b/error_template_test.go index 89de903..e9e6f82 100644 --- a/error_template_test.go +++ b/error_template_test.go @@ -137,71 +137,69 @@ func TestStackTraceRegex(t *testing.T) { t.Fatal("expected panic, got successful execution instead") }) - t.Run("Extract variables", func(t *testing.T) { - tmp, err := setupTemplate(lib.ErrorTemplate{ - Name: "A", - Pattern: "invalid input '(?P.*)'", - OnGenExplainFn: emptyExplainFn, - OnGenBugFixFn: emptyBugFixFn, - }) - if err != nil { - t.Fatal(err) - } +} +func TestExtractVariables(t *testing.T) { + tmp, err := setupTemplate(lib.ErrorTemplate{ + Name: "WithVarError", + Pattern: "invalid input '(?P.*)'", + OnGenExplainFn: emptyExplainFn, + OnGenBugFixFn: emptyBugFixFn, + }) + if err != nil { + t.Fatal(err) + } + + tmp2, err := setupTemplate(lib.ErrorTemplate{ + Name: "WithoutVarError", + Pattern: "invalid input '.*'", + OnGenExplainFn: emptyExplainFn, + OnGenBugFixFn: emptyBugFixFn, + }) + if err != nil { + t.Fatal(err) + } + + t.Run("Simple", func(t *testing.T) { variables := tmp.ExtractVariables("invalid input '123abc'\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1") exp := map[string]string{ "stacktrace": "\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", "input": "123abc", } + fmt.Printf("%q\n", variables) if !reflect.DeepEqual(variables, exp) { t.Fatalf("expected %v, got %v", exp, variables) } }) - t.Run("Extract variables no stack trace", func(t *testing.T) { - tmp, err := setupTemplate(lib.ErrorTemplate{ - Name: "A", - Pattern: "invalid input '(?P.*)'", - OnGenExplainFn: emptyExplainFn, - OnGenBugFixFn: emptyBugFixFn, - }) - if err != nil { - t.Fatal(err) - } - - variables := tmp.ExtractVariables("invalid input '123abc'") + t.Run("No stack trace", func(t *testing.T) { + variables := tmp.ExtractVariables("invalid input 'wxyz88@'") exp := map[string]string{ - "input": "123abc", + "input": "wxyz88@", "stacktrace": "", } + fmt.Printf("%q\n", variables) if !reflect.DeepEqual(variables, exp) { t.Fatalf("expected %v, got %v", exp, variables) } }) - t.Run("Extract variables no variables", func(t *testing.T) { - tmp, err := setupTemplate(lib.ErrorTemplate{ - Name: "A", - Pattern: "invalid input '123abc'", - OnGenExplainFn: emptyExplainFn, - OnGenBugFixFn: emptyBugFixFn, - }) - if err != nil { - t.Fatal(err) - } - - variables := tmp.ExtractVariables("invalid input '123abc'") + t.Run("No variables", func(t *testing.T) { + variables := tmp2.ExtractVariables("invalid input '123abc'") exp := map[string]string{ "stacktrace": "", } + fmt.Printf("%q\n", variables) if !reflect.DeepEqual(variables, exp) { t.Fatalf("expected %v, got %v", exp, variables) } }) +} +func TestExtractStackTrace(t *testing.T) { t.Run("Extract stack trace", func(t *testing.T) { tmp, err := setupTemplate(lib.ErrorTemplate{ Name: "A", From 8e868d4f1d0ac641b6e3ad86ef4d1befa586594f Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Tue, 30 Jan 2024 10:46:01 +0800 Subject: [PATCH 4/8] func: add indiviual match function for each error template, add check in TestExtractVariables --- error_template.go | 6 +++++- error_template_test.go | 21 ++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/error_template.go b/error_template.go index b28bd6b..b25b57f 100644 --- a/error_template.go +++ b/error_template.go @@ -93,6 +93,10 @@ func (tmp *CompiledErrorTemplate) ExtractStackTrace(cd *ContextData) TraceStack return traceStack } +func (tmp *CompiledErrorTemplate) Match(str string) bool { + return tmp.Pattern.MatchString(str) +} + type ErrorTemplates map[string]*CompiledErrorTemplate const defaultStackTraceRegex = `(?P(?:.|\s)*)` @@ -166,7 +170,7 @@ func (tmps ErrorTemplates) MustAdd(language *Language, template ErrorTemplate) * func (tmps ErrorTemplates) Match(msg string) *CompiledErrorTemplate { for _, tmp := range tmps { - if tmp.Pattern.MatchString(msg) { + if tmp.Match(msg) { return tmp } } diff --git a/error_template_test.go b/error_template_test.go index e9e6f82..7f1263a 100644 --- a/error_template_test.go +++ b/error_template_test.go @@ -161,7 +161,12 @@ func TestExtractVariables(t *testing.T) { } t.Run("Simple", func(t *testing.T) { - variables := tmp.ExtractVariables("invalid input '123abc'\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1") + input := "invalid input '123abc'\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1" + if !tmp.Match(input) { + t.Fatalf("expected template to match input, got false instead") + } + + variables := tmp.ExtractVariables(input) exp := map[string]string{ "stacktrace": "\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", "input": "123abc", @@ -174,7 +179,12 @@ func TestExtractVariables(t *testing.T) { }) t.Run("No stack trace", func(t *testing.T) { - variables := tmp.ExtractVariables("invalid input 'wxyz88@'") + input := "invalid input 'wxyz88@'" + if !tmp.Match(input) { + t.Fatalf("expected template to match input, got false instead") + } + + variables := tmp.ExtractVariables(input) exp := map[string]string{ "input": "wxyz88@", "stacktrace": "", @@ -187,7 +197,12 @@ func TestExtractVariables(t *testing.T) { }) t.Run("No variables", func(t *testing.T) { - variables := tmp2.ExtractVariables("invalid input '123abc'") + input := "invalid input '123abc'" + if !tmp2.Match(input) { + t.Fatalf("expected template to match input, got false instead") + } + + variables := tmp2.ExtractVariables(input) exp := map[string]string{ "stacktrace": "", } From 42c786406790b5761adf5842b8a74d2c842a4fc0 Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Tue, 30 Jan 2024 10:54:20 +0800 Subject: [PATCH 5/8] feat: add further checks in TestExtractVariables --- error_template_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/error_template_test.go b/error_template_test.go index 7f1263a..327609d 100644 --- a/error_template_test.go +++ b/error_template_test.go @@ -197,6 +197,23 @@ func TestExtractVariables(t *testing.T) { }) t.Run("No variables", func(t *testing.T) { + input := "invalid input '123abc'\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1" + if !tmp2.Match(input) { + t.Fatalf("expected template to match input, got false instead") + } + + variables := tmp2.ExtractVariables(input) + exp := map[string]string{ + "stacktrace": "\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", + } + + fmt.Printf("%q\n", variables) + if !reflect.DeepEqual(variables, exp) { + t.Fatalf("expected %v, got %v", exp, variables) + } + }) + + t.Run("No variables + no stack trace", func(t *testing.T) { input := "invalid input '123abc'" if !tmp2.Match(input) { t.Fatalf("expected template to match input, got false instead") From 02ba64d9df2f4f1fd931bc06f4969115d2ad64b7 Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Tue, 30 Jan 2024 11:05:36 +0800 Subject: [PATCH 6/8] feat: add test extract variables for multiple errors --- error_template_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/error_template_test.go b/error_template_test.go index 327609d..2eea9fd 100644 --- a/error_template_test.go +++ b/error_template_test.go @@ -229,6 +229,50 @@ func TestExtractVariables(t *testing.T) { t.Fatalf("expected %v, got %v", exp, variables) } }) + + t.Run("Multiple", func(t *testing.T) { + inputs := []string{ + "invalid input '123abc'\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", + "invalid input 'shouldNotBeIncluded'\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", + } + input := strings.Join(inputs, "\n\n") + if !tmp.Match(input) { + t.Fatalf("expected template to match input, got false instead") + } + + variables := tmp.ExtractVariables(input) + exp := map[string]string{ + "stacktrace": "\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", + "input": "123abc", + } + + fmt.Printf("%q\n", variables) + if !reflect.DeepEqual(variables, exp) { + t.Fatalf("expected %v, got %v", exp, variables) + } + }) + + t.Run("Multiple no stack trace", func(t *testing.T) { + inputs := []string{ + "invalid input '123abc'", + "invalid input 'shouldNotBeIncluded'", + } + input := strings.Join(inputs, "\n\n") + if !tmp.Match(input) { + t.Fatalf("expected template to match input, got false instead") + } + + variables := tmp.ExtractVariables(input) + exp := map[string]string{ + "stacktrace": "\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", + "input": "123abc", + } + + fmt.Printf("%q\n", variables) + if !reflect.DeepEqual(variables, exp) { + t.Fatalf("expected %v, got %v", exp, variables) + } + }) } func TestExtractStackTrace(t *testing.T) { From 6a4b95f639ebb773b524d2d1c97a9f650ad5c687 Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Tue, 30 Jan 2024 11:27:42 +0800 Subject: [PATCH 7/8] feat: add further test into ExtractStackTrace --- error_template_test.go | 95 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 10 deletions(-) diff --git a/error_template_test.go b/error_template_test.go index 2eea9fd..0455fed 100644 --- a/error_template_test.go +++ b/error_template_test.go @@ -276,17 +276,17 @@ func TestExtractVariables(t *testing.T) { } func TestExtractStackTrace(t *testing.T) { - t.Run("Extract stack trace", func(t *testing.T) { - tmp, err := setupTemplate(lib.ErrorTemplate{ - Name: "A", - Pattern: "invalid input '(?P.*)'", - OnGenExplainFn: emptyExplainFn, - OnGenBugFixFn: emptyBugFixFn, - }) - if err != nil { - t.Fatal(err) - } + tmp, err := setupTemplate(lib.ErrorTemplate{ + Name: "A", + Pattern: "invalid input '(?P.*)'", + OnGenExplainFn: emptyExplainFn, + OnGenBugFixFn: emptyBugFixFn, + }) + if err != nil { + t.Fatal(err) + } + t.Run("Simple", func(t *testing.T) { cd := lib.NewContextData(lib.NewEmptyStore(), "/home/user") cd.Variables = map[string]string{ "stacktrace": "\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", @@ -332,6 +332,81 @@ func TestExtractStackTrace(t *testing.T) { t.Fatalf("expected %v, got %v", exp, stackTrace) } }) + + t.Run("Empty", func(t *testing.T) { + cd := lib.NewContextData(lib.NewEmptyStore(), "/home/user") + cd.Variables = map[string]string{ + "stacktrace": "", + } + + stackTrace := tmp.ExtractStackTrace(cd) + exp := lib.TraceStack{} + + if !reflect.DeepEqual(stackTrace, exp) { + t.Fatalf("expected %v, got %v", exp, stackTrace) + } + }) + + t.Run("Invalid stack trace", func(t *testing.T) { + cd := lib.NewContextData(lib.NewEmptyStore(), "/home/user") + cd.Variables = map[string]string{ + "stacktrace": "\nin main at 123\nin main at 1", + } + + stackTrace := tmp.ExtractStackTrace(cd) + exp := lib.TraceStack{} + + if !reflect.DeepEqual(stackTrace, exp) { + t.Fatalf("expected %v, got %v", exp, stackTrace) + } + }) + + t.Run("Invalid in the middle", func(t *testing.T) { + cd := lib.NewContextData(lib.NewEmptyStore(), "/home/user") + cd.Variables = map[string]string{ + "stacktrace": "\nin funcB at /home/user/main.py:123\nin funcA at 123\nin main at /home/user/main.py:1", + } + + stackTrace := tmp.ExtractStackTrace(cd) + exp := lib.TraceStack{ + lib.StackTraceEntry{ + SymbolName: "funcB", + Location: lib.Location{ + DocumentPath: "/home/user/main.py", + StartPos: lib.Position{ + Line: 123, + Column: 0, + Index: 0, + }, + EndPos: lib.Position{ + Line: 123, + Column: 0, + Index: 0, + }, + }, + }, + lib.StackTraceEntry{ + SymbolName: "main", + Location: lib.Location{ + DocumentPath: "/home/user/main.py", + StartPos: lib.Position{ + Line: 1, + Column: 0, + Index: 0, + }, + EndPos: lib.Position{ + Line: 1, + Column: 0, + Index: 0, + }, + }, + }, + } + + if !reflect.DeepEqual(stackTrace, exp) { + t.Fatalf("expected %v, got %v", exp, stackTrace) + } + }) } func TestErrorTemplates(t *testing.T) { From c8894191e09031d7754ecea458806d162f9cc277 Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Tue, 30 Jan 2024 11:55:11 +0800 Subject: [PATCH 8/8] feat: add json printing to TestExtractStackTrace --- error_template_test.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/error_template_test.go b/error_template_test.go index 0455fed..faa8fae 100644 --- a/error_template_test.go +++ b/error_template_test.go @@ -1,6 +1,7 @@ package errgoengine_test import ( + "encoding/json" "fmt" "reflect" "strings" @@ -275,6 +276,11 @@ func TestExtractVariables(t *testing.T) { }) } +func printStackTraceJson(stackTrace lib.TraceStack) { + b, _ := json.MarshalIndent(stackTrace, "", " ") + fmt.Println(string(b)) +} + func TestExtractStackTrace(t *testing.T) { tmp, err := setupTemplate(lib.ErrorTemplate{ Name: "A", @@ -289,13 +295,13 @@ func TestExtractStackTrace(t *testing.T) { t.Run("Simple", func(t *testing.T) { cd := lib.NewContextData(lib.NewEmptyStore(), "/home/user") cd.Variables = map[string]string{ - "stacktrace": "\nin main at /home/user/main.py:123\nin main at /home/user/main.py:1", + "stacktrace": "\nin funcA at /home/user/main.py:123\nin main at /home/user/main.py:1", } stackTrace := tmp.ExtractStackTrace(cd) exp := lib.TraceStack{ lib.StackTraceEntry{ - SymbolName: "main", + SymbolName: "funcA", Location: lib.Location{ DocumentPath: "/home/user/main.py", StartPos: lib.Position{ @@ -328,6 +334,8 @@ func TestExtractStackTrace(t *testing.T) { }, } + printStackTraceJson(exp) + printStackTraceJson(stackTrace) if !reflect.DeepEqual(stackTrace, exp) { t.Fatalf("expected %v, got %v", exp, stackTrace) } @@ -342,6 +350,8 @@ func TestExtractStackTrace(t *testing.T) { stackTrace := tmp.ExtractStackTrace(cd) exp := lib.TraceStack{} + printStackTraceJson(exp) + printStackTraceJson(stackTrace) if !reflect.DeepEqual(stackTrace, exp) { t.Fatalf("expected %v, got %v", exp, stackTrace) } @@ -356,6 +366,8 @@ func TestExtractStackTrace(t *testing.T) { stackTrace := tmp.ExtractStackTrace(cd) exp := lib.TraceStack{} + printStackTraceJson(exp) + printStackTraceJson(stackTrace) if !reflect.DeepEqual(stackTrace, exp) { t.Fatalf("expected %v, got %v", exp, stackTrace) } @@ -403,6 +415,8 @@ func TestExtractStackTrace(t *testing.T) { }, } + printStackTraceJson(exp) + printStackTraceJson(stackTrace) if !reflect.DeepEqual(stackTrace, exp) { t.Fatalf("expected %v, got %v", exp, stackTrace) }