diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index 398f36e4..ba68f97e 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -28,5 +28,5 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - - name: Run end to end tests - run: ./end-to-end-tests/testrunner --no-clean + - name: Run validation tests + run: ./validation-tests/testrunner --no-clean diff --git a/cmd/instrument.go b/cmd/instrument.go index f129b4e5..6a1e0e8c 100644 --- a/cmd/instrument.go +++ b/cmd/instrument.go @@ -11,6 +11,12 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/dave/dst/decorator" + "github.com/newrelic/go-easy-instrumentation/integrations/nragent" + "github.com/newrelic/go-easy-instrumentation/integrations/nrgin" + "github.com/newrelic/go-easy-instrumentation/integrations/nrgochi" + "github.com/newrelic/go-easy-instrumentation/integrations/nrgrpc" + "github.com/newrelic/go-easy-instrumentation/integrations/nrnethttp" + "github.com/newrelic/go-easy-instrumentation/integrations/nrslog" "github.com/newrelic/go-easy-instrumentation/internal/comment" "github.com/newrelic/go-easy-instrumentation/parser" "github.com/spf13/cobra" @@ -27,6 +33,44 @@ const ( defaultDiffFileName = "new-relic-instrumentation.diff" ) +// registerIntegrations registers all integration tracing functions with the manager +// in the correct order (order matters for instrumentation!) +func registerIntegrations(manager *parser.InstrumentationManager) { + // Pre-instrumentation scanning phase (ORDER PRESERVED) + manager.LoadPreInstrumentationTracingFunctions( + parser.DetectTransactions, + parser.DetectErrors, + nrnethttp.DetectWrappedRoutes, + ) + + // Stateless tracing functions (ORDER PRESERVED) + manager.LoadStatelessTracingFunctions( + nragent.InstrumentMain, + nrnethttp.InstrumentHandleFunction, + nrnethttp.InstrumentHttpClient, + nrnethttp.CannotInstrumentHttpMethod, + nrgrpc.InstrumentGrpcDial, + nrgin.InstrumentGinFunction, + nrgrpc.InstrumentGrpcServerMethod, + nrslog.InstrumentSlogHandler, + ) + + // Stateful tracing functions (ORDER PRESERVED) + manager.LoadStatefulTracingFunctions( + nrnethttp.ExternalHttpCall, + nrnethttp.WrapNestedHandleFunction, + nrgrpc.InstrumentGrpcServer, + nrgin.InstrumentGinMiddleware, + nrgochi.InstrumentChiMiddleware, + nrgochi.InstrumentChiRouterLiteral, + ) + + // Fact discovery functions + manager.LoadDependencyScans( + nrgrpc.FindGrpcServerObject, + ) +} + var ( diffFile string excludeDirs string @@ -177,12 +221,14 @@ func instrumentPackages(packagePath string, patterns []string, outputFile string manager := parser.NewInstrumentationManager(pkgs, defaultAppName, defaultAgentVariableName, outputFile, packagePath) + // Register all integrations + registerIntegrations(manager) + steps := []struct { desc string fn func() error }{ {"Creating diff file", manager.CreateDiffFile}, - {"Detecting dependencies", manager.DetectDependencyIntegrations}, {"Tracing package calls", manager.TracePackageCalls}, {"Scanning application", manager.ScanApplication}, {"Instrumenting application", manager.InstrumentApplication}, @@ -229,7 +275,7 @@ func runTUIMode(packagePath string, patterns []string, outputFile string) { fn func() error }{ {"Creating diff file", manager.CreateDiffFile}, - {"Detecting dependencies", manager.DetectDependencyIntegrations}, + {"Detecting dependencies", func() error { registerIntegrations(manager); return nil }}, {"Tracing package calls", manager.TracePackageCalls}, {"Scanning application", manager.ScanApplication}, {"Instrumenting application", manager.InstrumentApplication}, diff --git a/cmd/instrument_test.go b/cmd/instrument_test.go index 5f4959f6..3cf9aaa1 100644 --- a/cmd/instrument_test.go +++ b/cmd/instrument_test.go @@ -309,7 +309,7 @@ func TestWaitForNext(t *testing.T) { // Integration tests for instrumentPackages func TestInstrumentPackages_BasicGin(t *testing.T) { - packagePath := filepath.Join("..", "end-to-end-tests", "gin-examples", "basic") + packagePath := filepath.Join("..", "validation-tests", "gin-examples", "basic") if _, err := os.Stat(packagePath); err != nil { t.Skipf("test fixture not found: %v", err) } @@ -332,7 +332,7 @@ func TestInstrumentPackages_BasicGin(t *testing.T) { } func TestInstrumentPackages_HttpApp(t *testing.T) { - packagePath := filepath.Join("..", "end-to-end-tests", "http-app") + packagePath := filepath.Join("..", "validation-tests", "http-app") if _, err := os.Stat(packagePath); err != nil { t.Skipf("test fixture not found: %v", err) } @@ -354,7 +354,7 @@ func TestInstrumentPackages_HttpApp(t *testing.T) { } func TestInstrumentPackages_CustomPatterns(t *testing.T) { - packagePath := filepath.Join("..", "end-to-end-tests", "gin-examples", "basic") + packagePath := filepath.Join("..", "validation-tests", "gin-examples", "basic") if _, err := os.Stat(packagePath); err != nil { t.Skipf("test fixture not found: %v", err) } diff --git a/cmd/interactive.go b/cmd/interactive.go index 5956b122..68f1d608 100644 --- a/cmd/interactive.go +++ b/cmd/interactive.go @@ -62,7 +62,7 @@ func scanGoFiles(root string, excludedDirs []string) ([]string, error) { base := info.Name() for _, excluded := range excludedDirs { // Simple check: if directory name matches excluded name exacty - // Or if path contains it? User asked to "exclude folders like end-to-end-tests". + // Or if path contains it? User asked to "exclude folders like validation-tests". // Let's do a strict component match to be safe, or just check if it matches the name. if base == excluded { return filepath.SkipDir diff --git a/codecov.yml b/codecov.yml index e5c682f5..3d7f097f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -4,7 +4,7 @@ coverage: precision: 2 ignore: - - end-to-end-tests/** + - validation-tests/** component_management: individual_components: diff --git a/end-to-end-tests/http-app/go.mod b/end-to-end-tests/http-app/go.mod deleted file mode 100644 index a1ca5df7..00000000 --- a/end-to-end-tests/http-app/go.mod +++ /dev/null @@ -1,20 +0,0 @@ -module http-app - -go 1.24 - -toolchain go1.24.11 - -require github.com/stretchr/testify v1.10.0 - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/newrelic/go-agent/v3 v3.42.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/end-to-end-tests/http-app/go.sum b/end-to-end-tests/http-app/go.sum deleted file mode 100644 index c2a5061f..00000000 --- a/end-to-end-tests/http-app/go.sum +++ /dev/null @@ -1,24 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/newrelic/go-agent/v3 v3.42.0 h1:aA2Ea1RT5eD59LtOS1KGFXSmaDs6kM3Jeqo7PpuQoFQ= -github.com/newrelic/go-agent/v3 v3.42.0/go.mod h1:sCgxDCVydoKD/C4S8BFxDtmFHvdWHtaIz/a3kiyNB/k= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/end-to-end-tests/mysql/go.mod b/end-to-end-tests/mysql/go.mod deleted file mode 100644 index 6222db59..00000000 --- a/end-to-end-tests/mysql/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module mysql - -go 1.24.0 diff --git a/end-to-end-tests/mysql/go.sum b/end-to-end-tests/mysql/go.sum deleted file mode 100644 index e69de29b..00000000 diff --git a/end-to-end-tests/testcases.json b/end-to-end-tests/testcases.json deleted file mode 100644 index e7f9a2a0..00000000 --- a/end-to-end-tests/testcases.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "tests": [ - { - "name": "http web app", - "dir": "end-to-end-tests/http-app" - }, - { - "name": "http-mux web app", - "dir": "end-to-end-tests/http-mux-app" - }, - { - "name": "grpc app", - "dir": "end-to-end-tests/grpc", - "builds": [ - "end-to-end-tests/grpc/server", - "end-to-end-tests/grpc/client" - ] - }, - { - "name": "gin - basic", - "dir": "end-to-end-tests/gin-examples/basic" - }, - { - "name": "gin - multiple services", - "dir": "end-to-end-tests/gin-examples/multiple-service" - }, - { - "name": "semi-instrumented - existing transactions", - "dir": "end-to-end-tests/semi-instrumented/existing-transactions" - }, - { - "name": "unit tests", - "dir": "end-to-end-tests/unit-tests" - }, - { - "name": "gochi app", - "dir": "end-to-end-tests/gochi" - }, - { - "name": "slog app", - "dir": "end-to-end-tests/slog-examples" - } - ] -} diff --git a/go.mod b/go.mod index 6dd188fe..30d769b8 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,15 @@ go 1.24.0 toolchain go1.24.11 require ( + github.com/charmbracelet/bubbles v0.21.0 + github.com/charmbracelet/bubbletea v1.3.10 + github.com/charmbracelet/lipgloss v1.1.0 github.com/dave/dst v0.27.3 github.com/newrelic/go-agent/v3 v3.42.0 github.com/sourcegraph/go-diff-patch v0.0.0-20240223163233-798fd1e94a8e github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 + golang.org/x/term v0.30.0 golang.org/x/tools v0.28.0 ) @@ -17,11 +21,7 @@ require golang.org/x/sync v0.12.0 // indirect require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/bubbles v0.21.0 // indirect - github.com/charmbracelet/bubbletea v1.3.10 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/harmonica v0.2.0 // indirect - github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect @@ -33,20 +33,17 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect - github.com/schollz/progressbar/v3 v3.19.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect google.golang.org/grpc v1.65.0 // indirect diff --git a/go.sum b/go.sum index 6abf4413..7a2ed4c2 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlv github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= -github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= -github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= @@ -26,8 +24,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -47,22 +43,14 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/newrelic/go-agent/v3 v3.40.1 h1:8nb4R252Fpuc3oySvlHpDwqySqaPWL5nf7ZVEhqtUeA= -github.com/newrelic/go-agent/v3 v3.40.1/go.mod h1:X0TLXDo+ttefTIue1V96Y5seb8H6wqf6uUq4UpPsYj8= github.com/newrelic/go-agent/v3 v3.42.0 h1:aA2Ea1RT5eD59LtOS1KGFXSmaDs6kM3Jeqo7PpuQoFQ= github.com/newrelic/go-agent/v3 v3.42.0/go.mod h1:sCgxDCVydoKD/C4S8BFxDtmFHvdWHtaIz/a3kiyNB/k= -github.com/newrelic/go-agent/v3 v3.42.0 h1:aA2Ea1RT5eD59LtOS1KGFXSmaDs6kM3Jeqo7PpuQoFQ= -github.com/newrelic/go-agent/v3 v3.42.0/go.mod h1:sCgxDCVydoKD/C4S8BFxDtmFHvdWHtaIz/a3kiyNB/k= -github.com/newrelic/go-agent/v3/integrations/nrmysql v1.2.2 h1:JtaJdL4y1hj5mH0JA2XIIIZtOsivsCmG0wsp3cGtoNo= -github.com/newrelic/go-agent/v3/integrations/nrmysql v1.2.2/go.mod h1:0JZ1gqlaBi9FUrQsg9LLZR357oDH4fGYYTbQQPhOd8o= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -73,8 +61,6 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc= -github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sourcegraph/go-diff-patch v0.0.0-20240223163233-798fd1e94a8e h1:H+jDTUeF+SVd4ApwnSFoew8ZwGNRfgb9EsZc7LcocAg= @@ -87,6 +73,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= @@ -95,8 +83,6 @@ golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= diff --git a/parser/agent.go b/integrations/nragent/agent.go similarity index 82% rename from parser/agent.go rename to integrations/nragent/agent.go index e6d8b2d1..ab61fb7f 100644 --- a/parser/agent.go +++ b/integrations/nragent/agent.go @@ -1,4 +1,4 @@ -package parser +package nragent import ( "fmt" @@ -12,6 +12,7 @@ import ( "github.com/newrelic/go-easy-instrumentation/internal/codegen" "github.com/newrelic/go-easy-instrumentation/internal/comment" "github.com/newrelic/go-easy-instrumentation/internal/util" + "github.com/newrelic/go-easy-instrumentation/parser" "github.com/newrelic/go-easy-instrumentation/parser/tracestate" ) @@ -48,7 +49,7 @@ func errorReturnIndex(v *dst.CallExpr, pkg *decorator.Package) (int, bool) { return 0, false } -func isNewRelicMethod(call *dst.CallExpr) bool { +func IsNewRelicMethod(call *dst.CallExpr) bool { sel, ok := call.Fun.(*dst.SelectorExpr) if ok { if pkg, ok := sel.X.(*dst.Ident); ok { @@ -87,23 +88,23 @@ func findErrorVariable(stmt *dst.AssignStmt, pkg *decorator.Package) dst.Expr { ////////////////////////////////////////////// // InstrumentMain looks for the main method of a program, and uses this as an instrumentation initialization and injection point -func InstrumentMain(manager *InstrumentationManager, c *dstutil.Cursor) { +func InstrumentMain(manager *parser.InstrumentationManager, c *dstutil.Cursor) { mainFunctionNode := c.Node() if decl, ok := mainFunctionNode.(*dst.FuncDecl); ok { - // Check functions return signatures for newrelic.Application and if it exists, load it into manager.setupFunc + // Check functions return signatures for newrelic.Application and if it exists, load it into manager.SetupFunc() // We don't want to propagate tracing into the setup function so later on in our trace function we will ignore it checkForExistingApplicationInFunctions(manager, c) if decl.Name.Name == "main" { if !checkForExistingApplicationInMain(manager, decl) { - comment.Debug(manager.getDecoratorPackage(), decl, "Injecting New Relic agent initialization into main()") - agentDecl := codegen.InitializeAgent(manager.appName, manager.agentVariableName) + comment.Debug(manager.GetDecoratorPackage(), decl, "Injecting New Relic agent initialization into main()") + agentDecl := InitializeAgent(manager.AppName(), manager.AgentVariableName()) decl.Body.List = append(agentDecl, decl.Body.List...) - comment.Debug(manager.getDecoratorPackage(), decl, "Injecting agent shutdown into main()") - decl.Body.List = append(decl.Body.List, codegen.ShutdownAgent(manager.agentVariableName)) + comment.Debug(manager.GetDecoratorPackage(), decl, "Injecting agent shutdown into main()") + decl.Body.List = append(decl.Body.List, ShutdownAgent(manager.AgentVariableName())) // add go-agent/v3/newrelic to imports - manager.addImport(codegen.NewRelicAgentImportPath) + manager.AddImport(codegen.NewRelicAgentImportPath) } - newMain, _ := TraceFunction(manager, decl, tracestate.Main(manager.agentVariableName)) + newMain, _ := parser.TraceFunction(manager, decl, tracestate.Main(manager.AgentVariableName())) // this will skip the tracing of this function in the outer tree walking algorithm c.Replace(newMain) @@ -113,7 +114,7 @@ func InstrumentMain(manager *InstrumentationManager, c *dstutil.Cursor) { // checkForExistingApplicationInFunctions calls functions related to application detection // It inspects the AST nodes within the cursor's scope to find any references to the New Relic application. -func checkForExistingApplicationInFunctions(manager *InstrumentationManager, c *dstutil.Cursor) { +func checkForExistingApplicationInFunctions(manager *parser.InstrumentationManager, c *dstutil.Cursor) { if c == nil { return } @@ -125,7 +126,7 @@ func checkForExistingApplicationInFunctions(manager *InstrumentationManager, c * } // Checks return values of a given function. If the function returns a new relic application, it is marked as a "setup" function -func checkFuncDeclForApplication(manager *InstrumentationManager, node dst.Node) bool { +func checkFuncDeclForApplication(manager *parser.InstrumentationManager, node dst.Node) bool { decl, ok := node.(*dst.FuncDecl) if !ok || decl.Type == nil || decl.Type.Results == nil { return false @@ -144,15 +145,15 @@ func checkFuncDeclForApplication(manager *InstrumentationManager, node dst.Node) } if ident.Path == codegen.NewRelicAgentImportPath && ident.Name == "Application" { - manager.setupFunc = decl + manager.SetSetupFunc(decl) return true } } return false } -func handleAssignStmtForAgentVariable(manager *InstrumentationManager, node dst.Node) bool { - if manager.setupFunc == nil { +func handleAssignStmtForAgentVariable(manager *parser.InstrumentationManager, node dst.Node) bool { + if manager.SetupFunc() == nil { return false } assign, ok := node.(*dst.AssignStmt) @@ -172,13 +173,13 @@ func handleAssignStmtForAgentVariable(manager *InstrumentationManager, node dst. } funcCall, ok := ident.Obj.Decl.(*dst.FuncDecl) - if !ok || manager.setupFunc != funcCall { + if !ok || manager.SetupFunc() != funcCall { continue } // This is our setup function. We can now get the appName! if ident, ok := assign.Lhs[pos].(*dst.Ident); ok { - manager.agentVariableName = ident.Name + manager.SetAgentVariableName(ident.Name) return true } } @@ -188,12 +189,12 @@ func handleAssignStmtForAgentVariable(manager *InstrumentationManager, node dst. // checkForExistingApplicationInMain checks for existing application in main. // If an application is detected in the main function, we mark that one // as a setup function and will not conduct tracing on it. -func checkForExistingApplicationInMain(manager *InstrumentationManager, decl *dst.FuncDecl) bool { +func checkForExistingApplicationInMain(manager *parser.InstrumentationManager, decl *dst.FuncDecl) bool { if decl == nil { return false } // App already exists in a setup function inside of main. - if manager.setupFunc != nil { + if manager.SetupFunc() != nil { return true } // No setup function detected, check for application initialization in main @@ -215,8 +216,8 @@ func checkForExistingApplicationInMain(manager *InstrumentationManager, decl *ds } if path.Path == codegen.NewRelicAgentImportPath { - manager.agentVariableName = assign.Lhs[0].(*dst.Ident).Name - manager.setupFunc = decl + manager.SetAgentVariableName(assign.Lhs[0].(*dst.Ident).Name) + manager.SetSetupFunc(decl) return true } } @@ -282,12 +283,12 @@ func shouldNoticeError(stmt dst.Stmt, pkg *decorator.Package, tracing *tracestat // NoticeError will check for the presence of an error.Error variable in the body at the index in bodyIndex. // If it finds that an error is returned, it will add a line after the assignment statement to capture an error // with a newrelic transaction. All transactions are assumed to be named "txn" -func NoticeError(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State, functionCallWasTraced bool) bool { +func NoticeError(manager *parser.InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State, functionCallWasTraced bool) bool { if tracing.IsMain() { return false } - pkg := manager.getDecoratorPackage() + pkg := manager.GetDecoratorPackage() switch nodeVal := stmt.(type) { case *dst.ReturnStmt: if functionCallWasTraced || c.Index() < 0 { @@ -318,9 +319,9 @@ func NoticeError(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Curs nodeVal.Results = slices.Delete(nodeVal.Results, i, i+1) nodeVal.Results = slices.Insert(nodeVal.Results, i, retVals...) } - cachedExpr := manager.errorCache.GetExpression() + cachedExpr := manager.ErrorCache().GetExpression() if cachedExpr != nil && util.AssertExpressionEqual(result, cachedExpr) { - manager.errorCache.Clear() + manager.ErrorCache().Clear() comment.Debug(pkg, stmt, "Injecting error nil check with NoticeError before return") capture := codegen.IfErrorNotNilNoticeError(cachedExpr, tracing.TransactionVariable()) capture.Decs.Before = dst.EmptyLine @@ -333,7 +334,7 @@ func NoticeError(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Curs NoticeError(manager, nodeVal.Init, c, tracing, functionCallWasTraced) } if shouldNoticeError(stmt, pkg, tracing) { - errExpr := manager.errorCache.GetExpression() + errExpr := manager.ErrorCache().GetExpression() if errExpr != nil { var stmtBlock dst.Stmt if nodeVal.Body != nil && len(nodeVal.Body.List) > 0 { @@ -341,7 +342,7 @@ func NoticeError(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Curs } comment.Debug(pkg, stmt, "Injecting NoticeError into error handling block") nodeVal.Body.List = append([]dst.Stmt{codegen.NoticeError(errExpr, tracing.TransactionVariable(), stmtBlock)}, nodeVal.Body.List...) - manager.errorCache.Clear() + manager.ErrorCache().Clear() return true } } @@ -361,11 +362,11 @@ func NoticeError(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Curs return false } - cachedErrExpr := manager.errorCache.GetExpression() + cachedErrExpr := manager.ErrorCache().GetExpression() if cachedErrExpr != nil { - stmt := manager.errorCache.GetStatement() + stmt := manager.ErrorCache().GetStatement() comment.Warn(pkg, stmt, stmt, fmt.Sprintf("Unchecked Error \"%s\", please consult New Relic documentation on error capture", util.WriteExpr(cachedErrExpr, pkg))) - manager.errorCache.Clear() + manager.ErrorCache().Clear() } // Always load the error into the cache @@ -381,8 +382,8 @@ func NoticeError(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Curs errStmt = parentStmt } } - if !manager.errorCache.IsExistingError(errExpr) { - manager.errorCache.Load(errExpr, errStmt) + if !manager.ErrorCache().IsExistingError(errExpr) { + manager.ErrorCache().Load(errExpr, errStmt) } } return false diff --git a/internal/codegen/agent.go b/integrations/nragent/codegen.go similarity index 86% rename from internal/codegen/agent.go rename to integrations/nragent/codegen.go index 547965c3..2ad60cc5 100644 --- a/internal/codegen/agent.go +++ b/integrations/nragent/codegen.go @@ -1,4 +1,4 @@ -package codegen +package nragent import ( "go/token" @@ -9,7 +9,7 @@ import ( const ( // the import path for the newrelic package NewRelicAgentImportPath string = "github.com/newrelic/go-agent/v3/newrelic" - agentErrorVariableName string = "agentInitError" + AgentErrorVariableName string = "agentInitError" ) func InitializeAgent(AppName, AgentVariableName string) []dst.Stmt { @@ -43,7 +43,7 @@ func InitializeAgent(AppName, AgentVariableName string) []dst.Stmt { Name: AgentVariableName, }, &dst.Ident{ - Name: agentErrorVariableName, + Name: AgentErrorVariableName, }, }, Tok: token.DEFINE, @@ -58,7 +58,7 @@ func InitializeAgent(AppName, AgentVariableName string) []dst.Stmt { }, } - return []dst.Stmt{agentInit, panicOnError(agentErrorVariableName)} + return []dst.Stmt{agentInit, PanicOnError(AgentErrorVariableName)} } func ShutdownAgent(AgentVariableName string) *dst.ExprStmt { @@ -94,7 +94,8 @@ func ShutdownAgent(AgentVariableName string) *dst.ExprStmt { } } -func panicOnError(errorVariableName string) *dst.IfStmt { +// PanicOnError creates an if statement that panics if the error variable is not nil (exported for tests) +func PanicOnError(errorVariableName string) *dst.IfStmt { return &dst.IfStmt{ Cond: &dst.BinaryExpr{ X: &dst.Ident{ diff --git a/internal/codegen/agent_test.go b/integrations/nragent/codegen_test.go similarity index 79% rename from internal/codegen/agent_test.go rename to integrations/nragent/codegen_test.go index d0ab9f99..c794422a 100644 --- a/internal/codegen/agent_test.go +++ b/integrations/nragent/codegen_test.go @@ -1,4 +1,4 @@ -package codegen +package nragent_test import ( "go/token" @@ -6,10 +6,11 @@ import ( "testing" "github.com/dave/dst" + "github.com/newrelic/go-easy-instrumentation/integrations/nragent" "github.com/stretchr/testify/assert" ) -func Test_InitializeAgent(t *testing.T) { +func TestInitializeAgent(t *testing.T) { type args struct { AppName string AgentVariableName string @@ -30,7 +31,7 @@ func Test_InitializeAgent(t *testing.T) { Name: "testAgent", }, &dst.Ident{ - Name: agentErrorVariableName, + Name: nragent.AgentErrorVariableName, }, }, Tok: token.DEFINE, @@ -38,19 +39,19 @@ func Test_InitializeAgent(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewApplication", - Path: NewRelicAgentImportPath, + Path: nragent.NewRelicAgentImportPath, }, Args: []dst.Expr{ &dst.CallExpr{ Fun: &dst.Ident{ - Path: NewRelicAgentImportPath, + Path: nragent.NewRelicAgentImportPath, Name: "ConfigFromEnvironment", }, }, }, }, }, - }, panicOnError(agentErrorVariableName)}, + }, nragent.PanicOnError(nragent.AgentErrorVariableName)}, }, { name: "Test create agent AST with AppName", @@ -64,7 +65,7 @@ func Test_InitializeAgent(t *testing.T) { Name: "testAgent", }, &dst.Ident{ - Name: agentErrorVariableName, + Name: nragent.AgentErrorVariableName, }, }, Tok: token.DEFINE, @@ -72,12 +73,12 @@ func Test_InitializeAgent(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewApplication", - Path: NewRelicAgentImportPath, + Path: nragent.NewRelicAgentImportPath, }, Args: []dst.Expr{ &dst.CallExpr{ Fun: &dst.Ident{ - Path: NewRelicAgentImportPath, + Path: nragent.NewRelicAgentImportPath, Name: "ConfigAppName", }, Args: []dst.Expr{ @@ -89,24 +90,24 @@ func Test_InitializeAgent(t *testing.T) { }, &dst.CallExpr{ Fun: &dst.Ident{ - Path: NewRelicAgentImportPath, + Path: nragent.NewRelicAgentImportPath, Name: "ConfigFromEnvironment", }, }, }, }, }, - }, panicOnError(agentErrorVariableName)}, + }, nragent.PanicOnError(nragent.AgentErrorVariableName)}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, InitializeAgent(tt.args.AppName, tt.args.AgentVariableName)) + assert.Equal(t, tt.want, nragent.InitializeAgent(tt.args.AppName, tt.args.AgentVariableName)) }) } } -func Test_shutdownAgent(t *testing.T) { +func TestShutdownAgent(t *testing.T) { type args struct { AgentVariableName string } @@ -154,13 +155,13 @@ func Test_shutdownAgent(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := ShutdownAgent(tt.args.AgentVariableName) + got := nragent.ShutdownAgent(tt.args.AgentVariableName) assert.Equal(t, tt.want, got) }) } } -func Test_panicOnError(t *testing.T) { +func TestPanicOnError(t *testing.T) { tests := []struct { name string want *dst.IfStmt @@ -203,7 +204,7 @@ func Test_panicOnError(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := panicOnError("err"); !reflect.DeepEqual(got, tt.want) { + if got := nragent.PanicOnError("err"); !reflect.DeepEqual(got, tt.want) { assert.Equal(t, tt.want, got) } }) diff --git a/parser/agent_test.go b/integrations/nragent/parsing_test.go similarity index 94% rename from parser/agent_test.go rename to integrations/nragent/parsing_test.go index a1b4e8cd..95fff136 100644 --- a/parser/agent_test.go +++ b/integrations/nragent/parsing_test.go @@ -1,14 +1,17 @@ -package parser +package nragent_test import ( "testing" "github.com/dave/dst" + "github.com/newrelic/go-easy-instrumentation/integrations/nragent" + "github.com/newrelic/go-easy-instrumentation/integrations/nrnethttp" "github.com/newrelic/go-easy-instrumentation/internal/codegen" + "github.com/newrelic/go-easy-instrumentation/parser" "github.com/stretchr/testify/assert" ) -func Test_isNewRelicMethod(t *testing.T) { +func TestIsNewRelicMethod(t *testing.T) { type args struct { call *dst.CallExpr } @@ -51,7 +54,7 @@ func Test_isNewRelicMethod(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "Get", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, }, @@ -60,14 +63,14 @@ func Test_isNewRelicMethod(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := isNewRelicMethod(tt.args.call); got != tt.want { - t.Errorf("isNewRelicMethod() = %v, want %v", got, tt.want) + if got := nragent.IsNewRelicMethod(tt.args.call); got != tt.want { + t.Errorf("nragent.IsNewRelicMethod() = %v, want %v", got, tt.want) } }) } } -func Test_noticeError(t *testing.T) { +func TestNoticeError(t *testing.T) { tests := []struct { name string code string @@ -316,8 +319,8 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentMain) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nragent.InstrumentMain) assert.Equal(t, tt.expect, got) }) } @@ -668,8 +671,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentMain) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nragent.InstrumentMain) assert.Equal(t, tt.expect, got) }) } diff --git a/internal/codegen/gin.go b/integrations/nrgin/codegen.go similarity index 70% rename from internal/codegen/gin.go rename to integrations/nrgin/codegen.go index 9277cc2e..7782fcf0 100644 --- a/internal/codegen/gin.go +++ b/integrations/nrgin/codegen.go @@ -1,4 +1,4 @@ -package codegen +package nrgin import ( "go/token" @@ -11,8 +11,12 @@ const ( GinImportPath = "github.com/gin-gonic/gin" ) -// GinMiddlewareCall returns a new relic gin middleware call, and a string representing the import path -// of the library that contains the middleware function +// NrGinMiddleware returns a Gin middleware call that instruments the router +// with New Relic. Returns the middleware statement and the import path. +// +// Example output: +// +// router.Use(nrgin.Middleware(app)) func NrGinMiddleware(routerName string, agentVariableName dst.Expr) (*dst.ExprStmt, string) { return &dst.ExprStmt{ X: &dst.CallExpr{ @@ -34,12 +38,17 @@ func NrGinMiddleware(routerName string, agentVariableName dst.Expr) (*dst.ExprSt }, }, NrginImportPath } + +// TxnFromGinContext generates code to extract a New Relic transaction from +// a Gin context. +// +// Example output: +// +// txn := nrgin.Transaction(c) func TxnFromGinContext(txnVariable string, ctxName string) *dst.AssignStmt { return &dst.AssignStmt{ Lhs: []dst.Expr{ - &dst.Ident{ - Name: txnVariable, - }, + &dst.Ident{Name: txnVariable}, }, Tok: token.DEFINE, Rhs: []dst.Expr{ @@ -49,9 +58,7 @@ func TxnFromGinContext(txnVariable string, ctxName string) *dst.AssignStmt { Path: NrginImportPath, }, Args: []dst.Expr{ - &dst.Ident{ - Name: ctxName, - }, + &dst.Ident{Name: ctxName}, }, }, }, diff --git a/internal/codegen/gin_test.go b/integrations/nrgin/codegen_test.go similarity index 71% rename from internal/codegen/gin_test.go rename to integrations/nrgin/codegen_test.go index e31776d5..0e8e258d 100644 --- a/internal/codegen/gin_test.go +++ b/integrations/nrgin/codegen_test.go @@ -1,6 +1,7 @@ -package codegen +package nrgin_test import ( + "github.com/newrelic/go-easy-instrumentation/integrations/nrgin" "go/token" "reflect" "testing" @@ -8,7 +9,7 @@ import ( "github.com/dave/dst" ) -func Test_NrGinMiddleware(t *testing.T) { +func TestNrGinMiddleware(t *testing.T) { type args struct { call *dst.CallExpr routerName string @@ -26,7 +27,7 @@ func Test_NrGinMiddleware(t *testing.T) { Fun: &dst.SelectorExpr{ X: &dst.Ident{ Name: "Default", - Path: GinImportPath, + Path: nrgin.GinImportPath, }, }, }, @@ -47,7 +48,7 @@ func Test_NrGinMiddleware(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "Middleware", - Path: NrginImportPath, + Path: nrgin.NrginImportPath, }, Args: []dst.Expr{ &dst.Ident{Name: "NewRelicApplication"}, @@ -60,18 +61,18 @@ func Test_NrGinMiddleware(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, imp := NrGinMiddleware(tt.args.routerName, tt.args.agentVariableName) + got, imp := nrgin.NrGinMiddleware(tt.args.routerName, tt.args.agentVariableName) if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NrGinMiddleware() = %v, want %v", got, tt.want) + t.Errorf("nrgin.NrGinMiddleware() = %v, want %v", got, tt.want) } - if imp != NrginImportPath { - t.Errorf("NrGinMiddleware() = %v, want %v", imp, NrginImportPath) + if imp != nrgin.NrginImportPath { + t.Errorf("nrgin.NrGinMiddleware() = %v, want %v", imp, nrgin.NrginImportPath) } }) } } -func Test_TxnFromGinContext(t *testing.T) { +func TestTxnFromGinContext(t *testing.T) { type args struct { txnVariable string ctxName string @@ -98,7 +99,7 @@ func Test_TxnFromGinContext(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "Transaction", - Path: NrginImportPath, + Path: nrgin.NrginImportPath, }, Args: []dst.Expr{ &dst.Ident{ @@ -117,8 +118,8 @@ func Test_TxnFromGinContext(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := TxnFromGinContext(tt.args.txnVariable, tt.args.ctxName); !reflect.DeepEqual(got, tt.want) { - t.Errorf("TxnFromGinContext() = %v, want %v", got, tt.want) + if got := nrgin.TxnFromGinContext(tt.args.txnVariable, tt.args.ctxName); !reflect.DeepEqual(got, tt.want) { + t.Errorf("nrgin.TxnFromGinContext() = %v, want %v", got, tt.want) } }) } diff --git a/end-to-end-tests/gin-examples/.gitignore b/integrations/nrgin/example/.gitignore similarity index 96% rename from end-to-end-tests/gin-examples/.gitignore rename to integrations/nrgin/example/.gitignore index 12dd971f..1caf4621 100644 --- a/end-to-end-tests/gin-examples/.gitignore +++ b/integrations/nrgin/example/.gitignore @@ -9,4 +9,4 @@ *.test # Output of the go coverage tool, specifically when used with LiteIDE -*.out +*.out \ No newline at end of file diff --git a/end-to-end-tests/gin-examples/LICENSE b/integrations/nrgin/example/LICENSE similarity index 100% rename from end-to-end-tests/gin-examples/LICENSE rename to integrations/nrgin/example/LICENSE diff --git a/end-to-end-tests/gin-examples/README.md b/integrations/nrgin/example/README.md similarity index 100% rename from end-to-end-tests/gin-examples/README.md rename to integrations/nrgin/example/README.md diff --git a/end-to-end-tests/gin-examples/basic/expect.ref b/integrations/nrgin/example/basic/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/basic/expect.ref rename to integrations/nrgin/example/basic/expect.ref diff --git a/end-to-end-tests/gin-examples/basic/main.go b/integrations/nrgin/example/basic/main.go similarity index 100% rename from end-to-end-tests/gin-examples/basic/main.go rename to integrations/nrgin/example/basic/main.go diff --git a/end-to-end-tests/gin-examples/basic/main_test.go b/integrations/nrgin/example/basic/main_test.go similarity index 100% rename from end-to-end-tests/gin-examples/basic/main_test.go rename to integrations/nrgin/example/basic/main_test.go diff --git a/end-to-end-tests/gin-examples/cookie/README.md b/integrations/nrgin/example/cookie/README.md similarity index 100% rename from end-to-end-tests/gin-examples/cookie/README.md rename to integrations/nrgin/example/cookie/README.md diff --git a/end-to-end-tests/gin-examples/cookie/expect.ref b/integrations/nrgin/example/cookie/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/cookie/expect.ref rename to integrations/nrgin/example/cookie/expect.ref diff --git a/end-to-end-tests/gin-examples/cookie/main.go b/integrations/nrgin/example/cookie/main.go similarity index 100% rename from end-to-end-tests/gin-examples/cookie/main.go rename to integrations/nrgin/example/cookie/main.go diff --git a/end-to-end-tests/gin-examples/custom-validation/expect.ref b/integrations/nrgin/example/custom-validation/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/custom-validation/expect.ref rename to integrations/nrgin/example/custom-validation/expect.ref diff --git a/end-to-end-tests/gin-examples/custom-validation/server.go b/integrations/nrgin/example/custom-validation/server.go similarity index 100% rename from end-to-end-tests/gin-examples/custom-validation/server.go rename to integrations/nrgin/example/custom-validation/server.go diff --git a/end-to-end-tests/gin-examples/file-binding/expect.ref b/integrations/nrgin/example/file-binding/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/file-binding/expect.ref rename to integrations/nrgin/example/file-binding/expect.ref diff --git a/end-to-end-tests/gin-examples/file-binding/main.go b/integrations/nrgin/example/file-binding/main.go similarity index 100% rename from end-to-end-tests/gin-examples/file-binding/main.go rename to integrations/nrgin/example/file-binding/main.go diff --git a/end-to-end-tests/gin-examples/file-binding/public/index.html b/integrations/nrgin/example/file-binding/public/index.html similarity index 100% rename from end-to-end-tests/gin-examples/file-binding/public/index.html rename to integrations/nrgin/example/file-binding/public/index.html diff --git a/end-to-end-tests/gin-examples/forward-proxy/README.md b/integrations/nrgin/example/forward-proxy/README.md similarity index 100% rename from end-to-end-tests/gin-examples/forward-proxy/README.md rename to integrations/nrgin/example/forward-proxy/README.md diff --git a/end-to-end-tests/gin-examples/forward-proxy/expect.ref b/integrations/nrgin/example/forward-proxy/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/forward-proxy/expect.ref rename to integrations/nrgin/example/forward-proxy/expect.ref diff --git a/end-to-end-tests/gin-examples/forward-proxy/main.go b/integrations/nrgin/example/forward-proxy/main.go similarity index 100% rename from end-to-end-tests/gin-examples/forward-proxy/main.go rename to integrations/nrgin/example/forward-proxy/main.go diff --git a/end-to-end-tests/gin-examples/go.mod b/integrations/nrgin/example/go.mod similarity index 87% rename from end-to-end-tests/gin-examples/go.mod rename to integrations/nrgin/example/go.mod index 64a46533..38775633 100644 --- a/end-to-end-tests/gin-examples/go.mod +++ b/integrations/nrgin/example/go.mod @@ -33,8 +33,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/newrelic/go-agent/v3 v3.42.0 // indirect - github.com/newrelic/go-agent/v3/integrations/nrgin v1.4.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -44,8 +42,6 @@ require ( golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/end-to-end-tests/gin-examples/go.sum b/integrations/nrgin/example/go.sum similarity index 92% rename from end-to-end-tests/gin-examples/go.sum rename to integrations/nrgin/example/go.sum index 4979c43a..fa4d12a3 100644 --- a/end-to-end-tests/gin-examples/go.sum +++ b/integrations/nrgin/example/go.sum @@ -57,10 +57,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/newrelic/go-agent/v3 v3.42.0 h1:aA2Ea1RT5eD59LtOS1KGFXSmaDs6kM3Jeqo7PpuQoFQ= -github.com/newrelic/go-agent/v3 v3.42.0/go.mod h1:sCgxDCVydoKD/C4S8BFxDtmFHvdWHtaIz/a3kiyNB/k= -github.com/newrelic/go-agent/v3/integrations/nrgin v1.4.2 h1:AdWN/9G5fkIgAUfnMnChr2ZL1jKbicZxNSsn99s4wgc= -github.com/newrelic/go-agent/v3/integrations/nrgin v1.4.2/go.mod h1:8mDVuKhV1U/NhuL8HLB0YxheDHCuo/dRqW4OgFiTMwI= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -103,10 +99,6 @@ golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/end-to-end-tests/gin-examples/graceful-shutdown/close/server.go b/integrations/nrgin/example/graceful-shutdown/close/server.go similarity index 100% rename from end-to-end-tests/gin-examples/graceful-shutdown/close/server.go rename to integrations/nrgin/example/graceful-shutdown/close/server.go diff --git a/end-to-end-tests/gin-examples/graceful-shutdown/expect.ref b/integrations/nrgin/example/graceful-shutdown/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/graceful-shutdown/expect.ref rename to integrations/nrgin/example/graceful-shutdown/expect.ref diff --git a/end-to-end-tests/gin-examples/graceful-shutdown/graceful-shutdown/notify-with-context/server.go b/integrations/nrgin/example/graceful-shutdown/graceful-shutdown/notify-with-context/server.go similarity index 100% rename from end-to-end-tests/gin-examples/graceful-shutdown/graceful-shutdown/notify-with-context/server.go rename to integrations/nrgin/example/graceful-shutdown/graceful-shutdown/notify-with-context/server.go diff --git a/end-to-end-tests/gin-examples/graceful-shutdown/graceful-shutdown/notify-without-context/server.go b/integrations/nrgin/example/graceful-shutdown/graceful-shutdown/notify-without-context/server.go similarity index 100% rename from end-to-end-tests/gin-examples/graceful-shutdown/graceful-shutdown/notify-without-context/server.go rename to integrations/nrgin/example/graceful-shutdown/graceful-shutdown/notify-without-context/server.go diff --git a/end-to-end-tests/gin-examples/group-routes/README.md b/integrations/nrgin/example/group-routes/README.md similarity index 100% rename from end-to-end-tests/gin-examples/group-routes/README.md rename to integrations/nrgin/example/group-routes/README.md diff --git a/end-to-end-tests/gin-examples/group-routes/expect.ref b/integrations/nrgin/example/group-routes/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/group-routes/expect.ref rename to integrations/nrgin/example/group-routes/expect.ref diff --git a/end-to-end-tests/gin-examples/group-routes/main.go b/integrations/nrgin/example/group-routes/main.go similarity index 100% rename from end-to-end-tests/gin-examples/group-routes/main.go rename to integrations/nrgin/example/group-routes/main.go diff --git a/end-to-end-tests/gin-examples/group-routes/routes/main.go b/integrations/nrgin/example/group-routes/routes/main.go similarity index 100% rename from end-to-end-tests/gin-examples/group-routes/routes/main.go rename to integrations/nrgin/example/group-routes/routes/main.go diff --git a/end-to-end-tests/gin-examples/group-routes/routes/ping.go b/integrations/nrgin/example/group-routes/routes/ping.go similarity index 100% rename from end-to-end-tests/gin-examples/group-routes/routes/ping.go rename to integrations/nrgin/example/group-routes/routes/ping.go diff --git a/end-to-end-tests/gin-examples/group-routes/routes/users.go b/integrations/nrgin/example/group-routes/routes/users.go similarity index 100% rename from end-to-end-tests/gin-examples/group-routes/routes/users.go rename to integrations/nrgin/example/group-routes/routes/users.go diff --git a/end-to-end-tests/gin-examples/grpc/example1/Makefile b/integrations/nrgin/example/grpc/example1/Makefile similarity index 100% rename from end-to-end-tests/gin-examples/grpc/example1/Makefile rename to integrations/nrgin/example/grpc/example1/Makefile diff --git a/end-to-end-tests/gin-examples/grpc/example1/README.md b/integrations/nrgin/example/grpc/example1/README.md similarity index 100% rename from end-to-end-tests/gin-examples/grpc/example1/README.md rename to integrations/nrgin/example/grpc/example1/README.md diff --git a/end-to-end-tests/gin-examples/grpc/example1/gen/helloworld/v1/helloworld.pb.go b/integrations/nrgin/example/grpc/example1/gen/helloworld/v1/helloworld.pb.go similarity index 100% rename from end-to-end-tests/gin-examples/grpc/example1/gen/helloworld/v1/helloworld.pb.go rename to integrations/nrgin/example/grpc/example1/gen/helloworld/v1/helloworld.pb.go diff --git a/end-to-end-tests/gin-examples/grpc/example1/gen/helloworld/v1/helloworld_grpc.pb.go b/integrations/nrgin/example/grpc/example1/gen/helloworld/v1/helloworld_grpc.pb.go similarity index 100% rename from end-to-end-tests/gin-examples/grpc/example1/gen/helloworld/v1/helloworld_grpc.pb.go rename to integrations/nrgin/example/grpc/example1/gen/helloworld/v1/helloworld_grpc.pb.go diff --git a/end-to-end-tests/gin-examples/grpc/example1/gin/main.go b/integrations/nrgin/example/grpc/example1/gin/main.go similarity index 100% rename from end-to-end-tests/gin-examples/grpc/example1/gin/main.go rename to integrations/nrgin/example/grpc/example1/gin/main.go diff --git a/end-to-end-tests/gin-examples/grpc/example1/go.mod b/integrations/nrgin/example/grpc/example1/go.mod similarity index 100% rename from end-to-end-tests/gin-examples/grpc/example1/go.mod rename to integrations/nrgin/example/grpc/example1/go.mod diff --git a/end-to-end-tests/gin-examples/grpc/example1/go.sum b/integrations/nrgin/example/grpc/example1/go.sum similarity index 100% rename from end-to-end-tests/gin-examples/grpc/example1/go.sum rename to integrations/nrgin/example/grpc/example1/go.sum diff --git a/end-to-end-tests/gin-examples/grpc/example1/grpc/server.go b/integrations/nrgin/example/grpc/example1/grpc/server.go similarity index 100% rename from end-to-end-tests/gin-examples/grpc/example1/grpc/server.go rename to integrations/nrgin/example/grpc/example1/grpc/server.go diff --git a/end-to-end-tests/gin-examples/grpc/example1/pb/helloworld/v1/helloworld.proto b/integrations/nrgin/example/grpc/example1/pb/helloworld/v1/helloworld.proto similarity index 100% rename from end-to-end-tests/gin-examples/grpc/example1/pb/helloworld/v1/helloworld.proto rename to integrations/nrgin/example/grpc/example1/pb/helloworld/v1/helloworld.proto diff --git a/end-to-end-tests/gin-examples/grpc/expect.ref b/integrations/nrgin/example/grpc/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/grpc/expect.ref rename to integrations/nrgin/example/grpc/expect.ref diff --git a/end-to-end-tests/gin-examples/http-pusher/assets/app.js b/integrations/nrgin/example/http-pusher/assets/app.js similarity index 100% rename from end-to-end-tests/gin-examples/http-pusher/assets/app.js rename to integrations/nrgin/example/http-pusher/assets/app.js diff --git a/end-to-end-tests/gin-examples/http-pusher/expect.ref b/integrations/nrgin/example/http-pusher/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/http-pusher/expect.ref rename to integrations/nrgin/example/http-pusher/expect.ref diff --git a/end-to-end-tests/gin-examples/http-pusher/main.go b/integrations/nrgin/example/http-pusher/main.go similarity index 100% rename from end-to-end-tests/gin-examples/http-pusher/main.go rename to integrations/nrgin/example/http-pusher/main.go diff --git a/end-to-end-tests/gin-examples/http-pusher/testdata/ca.pem b/integrations/nrgin/example/http-pusher/testdata/ca.pem similarity index 100% rename from end-to-end-tests/gin-examples/http-pusher/testdata/ca.pem rename to integrations/nrgin/example/http-pusher/testdata/ca.pem diff --git a/end-to-end-tests/gin-examples/http-pusher/testdata/server.key b/integrations/nrgin/example/http-pusher/testdata/server.key similarity index 100% rename from end-to-end-tests/gin-examples/http-pusher/testdata/server.key rename to integrations/nrgin/example/http-pusher/testdata/server.key diff --git a/end-to-end-tests/gin-examples/http-pusher/testdata/server.pem b/integrations/nrgin/example/http-pusher/testdata/server.pem similarity index 100% rename from end-to-end-tests/gin-examples/http-pusher/testdata/server.pem rename to integrations/nrgin/example/http-pusher/testdata/server.pem diff --git a/end-to-end-tests/gin-examples/http2/README.md b/integrations/nrgin/example/http2/README.md similarity index 100% rename from end-to-end-tests/gin-examples/http2/README.md rename to integrations/nrgin/example/http2/README.md diff --git a/end-to-end-tests/gin-examples/http2/expect.ref b/integrations/nrgin/example/http2/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/http2/expect.ref rename to integrations/nrgin/example/http2/expect.ref diff --git a/end-to-end-tests/gin-examples/http2/main.go b/integrations/nrgin/example/http2/main.go similarity index 100% rename from end-to-end-tests/gin-examples/http2/main.go rename to integrations/nrgin/example/http2/main.go diff --git a/end-to-end-tests/gin-examples/http2/testdata/ca.pem b/integrations/nrgin/example/http2/testdata/ca.pem similarity index 100% rename from end-to-end-tests/gin-examples/http2/testdata/ca.pem rename to integrations/nrgin/example/http2/testdata/ca.pem diff --git a/end-to-end-tests/gin-examples/http2/testdata/server.key b/integrations/nrgin/example/http2/testdata/server.key similarity index 100% rename from end-to-end-tests/gin-examples/http2/testdata/server.key rename to integrations/nrgin/example/http2/testdata/server.key diff --git a/end-to-end-tests/gin-examples/http2/testdata/server.pem b/integrations/nrgin/example/http2/testdata/server.pem similarity index 100% rename from end-to-end-tests/gin-examples/http2/testdata/server.pem rename to integrations/nrgin/example/http2/testdata/server.pem diff --git a/end-to-end-tests/gin-examples/multiple-service/expect.ref b/integrations/nrgin/example/multiple-service/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/multiple-service/expect.ref rename to integrations/nrgin/example/multiple-service/expect.ref diff --git a/end-to-end-tests/gin-examples/multiple-service/main.go b/integrations/nrgin/example/multiple-service/main.go similarity index 100% rename from end-to-end-tests/gin-examples/multiple-service/main.go rename to integrations/nrgin/example/multiple-service/main.go diff --git a/end-to-end-tests/gin-examples/ratelimiter/README.md b/integrations/nrgin/example/ratelimiter/README.md similarity index 100% rename from end-to-end-tests/gin-examples/ratelimiter/README.md rename to integrations/nrgin/example/ratelimiter/README.md diff --git a/end-to-end-tests/gin-examples/ratelimiter/expect.ref b/integrations/nrgin/example/ratelimiter/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/ratelimiter/expect.ref rename to integrations/nrgin/example/ratelimiter/expect.ref diff --git a/end-to-end-tests/gin-examples/ratelimiter/rate.go b/integrations/nrgin/example/ratelimiter/rate.go similarity index 100% rename from end-to-end-tests/gin-examples/ratelimiter/rate.go rename to integrations/nrgin/example/ratelimiter/rate.go diff --git a/end-to-end-tests/gin-examples/realtime-advanced/Makefile b/integrations/nrgin/example/realtime-advanced/Makefile similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/Makefile rename to integrations/nrgin/example/realtime-advanced/Makefile diff --git a/end-to-end-tests/gin-examples/realtime-advanced/expect.ref b/integrations/nrgin/example/realtime-advanced/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/expect.ref rename to integrations/nrgin/example/realtime-advanced/expect.ref diff --git a/end-to-end-tests/gin-examples/realtime-advanced/go.mod b/integrations/nrgin/example/realtime-advanced/go.mod similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/go.mod rename to integrations/nrgin/example/realtime-advanced/go.mod diff --git a/end-to-end-tests/gin-examples/realtime-advanced/go.sum b/integrations/nrgin/example/realtime-advanced/go.sum similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/go.sum rename to integrations/nrgin/example/realtime-advanced/go.sum diff --git a/end-to-end-tests/gin-examples/realtime-advanced/main.go b/integrations/nrgin/example/realtime-advanced/main.go similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/main.go rename to integrations/nrgin/example/realtime-advanced/main.go diff --git a/end-to-end-tests/gin-examples/realtime-advanced/resources/room_login.templ.html b/integrations/nrgin/example/realtime-advanced/resources/room_login.templ.html similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/resources/room_login.templ.html rename to integrations/nrgin/example/realtime-advanced/resources/room_login.templ.html diff --git a/end-to-end-tests/gin-examples/realtime-advanced/resources/static/epoch.min.css b/integrations/nrgin/example/realtime-advanced/resources/static/epoch.min.css similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/resources/static/epoch.min.css rename to integrations/nrgin/example/realtime-advanced/resources/static/epoch.min.css diff --git a/end-to-end-tests/gin-examples/realtime-advanced/resources/static/epoch.min.js b/integrations/nrgin/example/realtime-advanced/resources/static/epoch.min.js similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/resources/static/epoch.min.js rename to integrations/nrgin/example/realtime-advanced/resources/static/epoch.min.js diff --git a/end-to-end-tests/gin-examples/realtime-advanced/resources/static/prismjs.min.css b/integrations/nrgin/example/realtime-advanced/resources/static/prismjs.min.css similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/resources/static/prismjs.min.css rename to integrations/nrgin/example/realtime-advanced/resources/static/prismjs.min.css diff --git a/end-to-end-tests/gin-examples/realtime-advanced/resources/static/prismjs.min.js b/integrations/nrgin/example/realtime-advanced/resources/static/prismjs.min.js similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/resources/static/prismjs.min.js rename to integrations/nrgin/example/realtime-advanced/resources/static/prismjs.min.js diff --git a/end-to-end-tests/gin-examples/realtime-advanced/resources/static/realtime.js b/integrations/nrgin/example/realtime-advanced/resources/static/realtime.js similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/resources/static/realtime.js rename to integrations/nrgin/example/realtime-advanced/resources/static/realtime.js diff --git a/end-to-end-tests/gin-examples/realtime-advanced/rooms.go b/integrations/nrgin/example/realtime-advanced/rooms.go similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/rooms.go rename to integrations/nrgin/example/realtime-advanced/rooms.go diff --git a/end-to-end-tests/gin-examples/realtime-advanced/routes.go b/integrations/nrgin/example/realtime-advanced/routes.go similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/routes.go rename to integrations/nrgin/example/realtime-advanced/routes.go diff --git a/end-to-end-tests/gin-examples/realtime-advanced/stats.go b/integrations/nrgin/example/realtime-advanced/stats.go similarity index 100% rename from end-to-end-tests/gin-examples/realtime-advanced/stats.go rename to integrations/nrgin/example/realtime-advanced/stats.go diff --git a/end-to-end-tests/gin-examples/realtime-chat/Makefile b/integrations/nrgin/example/realtime-chat/Makefile similarity index 100% rename from end-to-end-tests/gin-examples/realtime-chat/Makefile rename to integrations/nrgin/example/realtime-chat/Makefile diff --git a/end-to-end-tests/gin-examples/realtime-chat/expect.ref b/integrations/nrgin/example/realtime-chat/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/realtime-chat/expect.ref rename to integrations/nrgin/example/realtime-chat/expect.ref diff --git a/end-to-end-tests/gin-examples/realtime-chat/go.mod b/integrations/nrgin/example/realtime-chat/go.mod similarity index 100% rename from end-to-end-tests/gin-examples/realtime-chat/go.mod rename to integrations/nrgin/example/realtime-chat/go.mod diff --git a/end-to-end-tests/gin-examples/realtime-chat/go.sum b/integrations/nrgin/example/realtime-chat/go.sum similarity index 100% rename from end-to-end-tests/gin-examples/realtime-chat/go.sum rename to integrations/nrgin/example/realtime-chat/go.sum diff --git a/end-to-end-tests/gin-examples/realtime-chat/main.go b/integrations/nrgin/example/realtime-chat/main.go similarity index 100% rename from end-to-end-tests/gin-examples/realtime-chat/main.go rename to integrations/nrgin/example/realtime-chat/main.go diff --git a/end-to-end-tests/gin-examples/realtime-chat/rooms.go b/integrations/nrgin/example/realtime-chat/rooms.go similarity index 100% rename from end-to-end-tests/gin-examples/realtime-chat/rooms.go rename to integrations/nrgin/example/realtime-chat/rooms.go diff --git a/end-to-end-tests/gin-examples/realtime-chat/template.go b/integrations/nrgin/example/realtime-chat/template.go similarity index 100% rename from end-to-end-tests/gin-examples/realtime-chat/template.go rename to integrations/nrgin/example/realtime-chat/template.go diff --git a/end-to-end-tests/gin-examples/reverse-proxy/README.md b/integrations/nrgin/example/reverse-proxy/README.md similarity index 100% rename from end-to-end-tests/gin-examples/reverse-proxy/README.md rename to integrations/nrgin/example/reverse-proxy/README.md diff --git a/end-to-end-tests/gin-examples/reverse-proxy/expect.ref b/integrations/nrgin/example/reverse-proxy/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/reverse-proxy/expect.ref rename to integrations/nrgin/example/reverse-proxy/expect.ref diff --git a/end-to-end-tests/gin-examples/reverse-proxy/realServer/main.go b/integrations/nrgin/example/reverse-proxy/realServer/main.go similarity index 100% rename from end-to-end-tests/gin-examples/reverse-proxy/realServer/main.go rename to integrations/nrgin/example/reverse-proxy/realServer/main.go diff --git a/end-to-end-tests/gin-examples/reverse-proxy/reverseServer/main.go b/integrations/nrgin/example/reverse-proxy/reverseServer/main.go similarity index 100% rename from end-to-end-tests/gin-examples/reverse-proxy/reverseServer/main.go rename to integrations/nrgin/example/reverse-proxy/reverseServer/main.go diff --git a/end-to-end-tests/gin-examples/secure-web-app/README.md b/integrations/nrgin/example/secure-web-app/README.md similarity index 100% rename from end-to-end-tests/gin-examples/secure-web-app/README.md rename to integrations/nrgin/example/secure-web-app/README.md diff --git a/end-to-end-tests/gin-examples/secure-web-app/expect.ref b/integrations/nrgin/example/secure-web-app/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/secure-web-app/expect.ref rename to integrations/nrgin/example/secure-web-app/expect.ref diff --git a/end-to-end-tests/gin-examples/secure-web-app/main.go b/integrations/nrgin/example/secure-web-app/main.go similarity index 100% rename from end-to-end-tests/gin-examples/secure-web-app/main.go rename to integrations/nrgin/example/secure-web-app/main.go diff --git a/end-to-end-tests/gin-examples/send_chunked_data/expect.ref b/integrations/nrgin/example/send_chunked_data/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/send_chunked_data/expect.ref rename to integrations/nrgin/example/send_chunked_data/expect.ref diff --git a/end-to-end-tests/gin-examples/send_chunked_data/send_chunked_data.go b/integrations/nrgin/example/send_chunked_data/send_chunked_data.go similarity index 100% rename from end-to-end-tests/gin-examples/send_chunked_data/send_chunked_data.go rename to integrations/nrgin/example/send_chunked_data/send_chunked_data.go diff --git a/end-to-end-tests/gin-examples/server-sent-event/README.md b/integrations/nrgin/example/server-sent-event/README.md similarity index 100% rename from end-to-end-tests/gin-examples/server-sent-event/README.md rename to integrations/nrgin/example/server-sent-event/README.md diff --git a/end-to-end-tests/gin-examples/server-sent-event/expect.ref b/integrations/nrgin/example/server-sent-event/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/server-sent-event/expect.ref rename to integrations/nrgin/example/server-sent-event/expect.ref diff --git a/end-to-end-tests/gin-examples/server-sent-event/main.go b/integrations/nrgin/example/server-sent-event/main.go similarity index 100% rename from end-to-end-tests/gin-examples/server-sent-event/main.go rename to integrations/nrgin/example/server-sent-event/main.go diff --git a/end-to-end-tests/gin-examples/server-sent-event/public/index.html b/integrations/nrgin/example/server-sent-event/public/index.html similarity index 100% rename from end-to-end-tests/gin-examples/server-sent-event/public/index.html rename to integrations/nrgin/example/server-sent-event/public/index.html diff --git a/end-to-end-tests/gin-examples/struct-lvl-validations/README.md b/integrations/nrgin/example/struct-lvl-validations/README.md similarity index 100% rename from end-to-end-tests/gin-examples/struct-lvl-validations/README.md rename to integrations/nrgin/example/struct-lvl-validations/README.md diff --git a/end-to-end-tests/gin-examples/struct-lvl-validations/expect.ref b/integrations/nrgin/example/struct-lvl-validations/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/struct-lvl-validations/expect.ref rename to integrations/nrgin/example/struct-lvl-validations/expect.ref diff --git a/end-to-end-tests/gin-examples/struct-lvl-validations/server.go b/integrations/nrgin/example/struct-lvl-validations/server.go similarity index 100% rename from end-to-end-tests/gin-examples/struct-lvl-validations/server.go rename to integrations/nrgin/example/struct-lvl-validations/server.go diff --git a/end-to-end-tests/gin-examples/template/expect.ref b/integrations/nrgin/example/template/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/template/expect.ref rename to integrations/nrgin/example/template/expect.ref diff --git a/end-to-end-tests/gin-examples/template/main.go b/integrations/nrgin/example/template/main.go similarity index 100% rename from end-to-end-tests/gin-examples/template/main.go rename to integrations/nrgin/example/template/main.go diff --git a/end-to-end-tests/gin-examples/upload-file/expect.ref b/integrations/nrgin/example/upload-file/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/upload-file/expect.ref rename to integrations/nrgin/example/upload-file/expect.ref diff --git a/end-to-end-tests/gin-examples/upload-file/multiple/main.go b/integrations/nrgin/example/upload-file/multiple/main.go similarity index 100% rename from end-to-end-tests/gin-examples/upload-file/multiple/main.go rename to integrations/nrgin/example/upload-file/multiple/main.go diff --git a/end-to-end-tests/gin-examples/upload-file/multiple/public/index.html b/integrations/nrgin/example/upload-file/multiple/public/index.html similarity index 100% rename from end-to-end-tests/gin-examples/upload-file/multiple/public/index.html rename to integrations/nrgin/example/upload-file/multiple/public/index.html diff --git a/end-to-end-tests/gin-examples/upload-file/single/main.go b/integrations/nrgin/example/upload-file/single/main.go similarity index 100% rename from end-to-end-tests/gin-examples/upload-file/single/main.go rename to integrations/nrgin/example/upload-file/single/main.go diff --git a/end-to-end-tests/gin-examples/upload-file/single/public/index.html b/integrations/nrgin/example/upload-file/single/public/index.html similarity index 100% rename from end-to-end-tests/gin-examples/upload-file/single/public/index.html rename to integrations/nrgin/example/upload-file/single/public/index.html diff --git a/end-to-end-tests/gin-examples/versioning/README.md b/integrations/nrgin/example/versioning/README.md similarity index 100% rename from end-to-end-tests/gin-examples/versioning/README.md rename to integrations/nrgin/example/versioning/README.md diff --git a/end-to-end-tests/gin-examples/versioning/expect.ref b/integrations/nrgin/example/versioning/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/versioning/expect.ref rename to integrations/nrgin/example/versioning/expect.ref diff --git a/end-to-end-tests/gin-examples/versioning/main.go b/integrations/nrgin/example/versioning/main.go similarity index 100% rename from end-to-end-tests/gin-examples/versioning/main.go rename to integrations/nrgin/example/versioning/main.go diff --git a/end-to-end-tests/gin-examples/websocket/README.md b/integrations/nrgin/example/websocket/README.md similarity index 100% rename from end-to-end-tests/gin-examples/websocket/README.md rename to integrations/nrgin/example/websocket/README.md diff --git a/end-to-end-tests/gin-examples/websocket/client/client.go b/integrations/nrgin/example/websocket/client/client.go similarity index 100% rename from end-to-end-tests/gin-examples/websocket/client/client.go rename to integrations/nrgin/example/websocket/client/client.go diff --git a/end-to-end-tests/gin-examples/websocket/expect.ref b/integrations/nrgin/example/websocket/expect.ref similarity index 100% rename from end-to-end-tests/gin-examples/websocket/expect.ref rename to integrations/nrgin/example/websocket/expect.ref diff --git a/end-to-end-tests/gin-examples/websocket/go.mod b/integrations/nrgin/example/websocket/go.mod similarity index 100% rename from end-to-end-tests/gin-examples/websocket/go.mod rename to integrations/nrgin/example/websocket/go.mod diff --git a/end-to-end-tests/gin-examples/websocket/go.sum b/integrations/nrgin/example/websocket/go.sum similarity index 100% rename from end-to-end-tests/gin-examples/websocket/go.sum rename to integrations/nrgin/example/websocket/go.sum diff --git a/end-to-end-tests/gin-examples/websocket/server/server.go b/integrations/nrgin/example/websocket/server/server.go similarity index 100% rename from end-to-end-tests/gin-examples/websocket/server/server.go rename to integrations/nrgin/example/websocket/server/server.go diff --git a/parser/gin.go b/integrations/nrgin/gin.go similarity index 71% rename from parser/gin.go rename to integrations/nrgin/gin.go index ebd8595a..741fcb6e 100644 --- a/parser/gin.go +++ b/integrations/nrgin/gin.go @@ -1,4 +1,4 @@ -package parser +package nrgin import ( "fmt" @@ -9,6 +9,7 @@ import ( "github.com/newrelic/go-easy-instrumentation/internal/codegen" "github.com/newrelic/go-easy-instrumentation/internal/comment" "github.com/newrelic/go-easy-instrumentation/internal/util" + "github.com/newrelic/go-easy-instrumentation/parser" "github.com/newrelic/go-easy-instrumentation/parser/tracestate" ) @@ -18,7 +19,7 @@ const ( ) // ginMiddlewareCall returns the variable name of the gin router so that new relic middleware can be appended -func ginMiddlewareCall(stmt dst.Stmt) string { +func GinMiddlewareCall(stmt dst.Stmt) string { v, ok := stmt.(*dst.AssignStmt) if !ok || len(v.Rhs) != 1 { return "" @@ -38,7 +39,7 @@ func ginMiddlewareCall(stmt dst.Stmt) string { // getGinContextFromHandler checks the type of a function or function literal declaration to determine if // this is a Gin handler. returns the context variable of the gin handler -func getGinContextFromHandler(nodeType *dst.FuncType, pkg *decorator.Package) string { +func GetGinContextFromHandler(nodeType *dst.FuncType, pkg *decorator.Package) string { // gin functions should only have 1 parameter if len(nodeType.Params.List) != 1 { return "" @@ -62,9 +63,9 @@ func getGinContextFromHandler(nodeType *dst.FuncType, pkg *decorator.Package) st } // defineTxnFromGinCtx injects a line of code that extracts a transaction from the gin context into the function body -func defineTxnFromGinCtx(body *dst.BlockStmt, txnVariable string, ctxName string) { +func DefineTxnFromGinCtx(body *dst.BlockStmt, txnVariable string, ctxName string) { stmts := make([]dst.Stmt, len(body.List)+1) - stmts[0] = codegen.TxnFromGinContext(txnVariable, ctxName) + stmts[0] = TxnFromGinContext(txnVariable, ctxName) for i, stmt := range body.List { stmts[i+1] = stmt } @@ -76,17 +77,17 @@ func defineTxnFromGinCtx(body *dst.BlockStmt, txnVariable string, ctxName string // WrapHandleFunction is a function that wraps net/http.HandeFunc() declarations inside of functions // that are being traced by a transaction. -func InstrumentGinMiddleware(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { +func InstrumentGinMiddleware(manager *parser.InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { // Check if any return true for ginMiddlewareCall - routerName := ginMiddlewareCall(stmt) + routerName := GinMiddlewareCall(stmt) if routerName == "" { return false } // Append at the current stmt location - middleware, goGet := codegen.NrGinMiddleware(routerName, tracing.AgentVariable()) - comment.Debug(manager.getDecoratorPackage(), stmt, fmt.Sprintf("Injecting nrgin middleware for router: %s", routerName)) + middleware, goGet := NrGinMiddleware(routerName, tracing.AgentVariable()) + comment.Debug(manager.GetDecoratorPackage(), stmt, fmt.Sprintf("Injecting nrgin middleware for router: %s", routerName)) c.InsertAfter(middleware) - manager.addImport(goGet) + manager.AddImport(goGet) return true } @@ -96,35 +97,35 @@ func InstrumentGinMiddleware(manager *InstrumentationManager, stmt dst.Stmt, c * // InstrumentGinFunction verifies gin function calls and initiates tracing. // If tracing was added, then defineTxnFromGinCtx is called to inject the transaction // into the function body via the gin context -func InstrumentGinFunction(manager *InstrumentationManager, c *dstutil.Cursor) { +func InstrumentGinFunction(manager *parser.InstrumentationManager, c *dstutil.Cursor) { currentNode := c.Node() switch v := currentNode.(type) { case *dst.FuncDecl: - ctxName := getGinContextFromHandler(v.Type, manager.getDecoratorPackage()) + ctxName := GetGinContextFromHandler(v.Type, manager.GetDecoratorPackage()) if ctxName == "" { return } - comment.Debug(manager.getDecoratorPackage(), v, fmt.Sprintf("Instrumenting gin handler: %s", v.Name.Name)) + comment.Debug(manager.GetDecoratorPackage(), v, fmt.Sprintf("Instrumenting gin handler: %s", v.Name.Name)) funcDecl := currentNode.(*dst.FuncDecl) txnName := codegen.DefaultTransactionVariable - _, ok := TraceFunction(manager, funcDecl, tracestate.FunctionBody(txnName)) + _, ok := parser.TraceFunction(manager, funcDecl, tracestate.FunctionBody(txnName)) if ok { - defineTxnFromGinCtx(funcDecl.Body, txnName, ctxName) + DefineTxnFromGinCtx(funcDecl.Body, txnName, ctxName) } case *dst.FuncLit: - ctxName := getGinContextFromHandler(v.Type, manager.getDecoratorPackage()) + ctxName := GetGinContextFromHandler(v.Type, manager.GetDecoratorPackage()) if ctxName == "" { return } - comment.Debug(manager.getDecoratorPackage(), v, "Instrumenting gin handler function literal") + comment.Debug(manager.GetDecoratorPackage(), v, "Instrumenting gin handler function literal") funcLit := currentNode.(*dst.FuncLit) txnName := codegen.DefaultTransactionVariable - tc := tracestate.FunctionBody(codegen.DefaultTransactionVariable).FuncLiteralDeclaration(manager.getDecoratorPackage(), funcLit) + tc := tracestate.FunctionBody(codegen.DefaultTransactionVariable).FuncLiteralDeclaration(manager.GetDecoratorPackage(), funcLit) tc.CreateSegment(funcLit) - defineTxnFromGinCtx(funcLit.Body, txnName, ctxName) - comment.Warn(manager.getDecoratorPackage(), c.Parent(), c.Node(), "function literal segments will be named \"function literal\" by default", "declare a function instead to improve segment name generation") + DefineTxnFromGinCtx(funcLit.Body, txnName, ctxName) + comment.Warn(manager.GetDecoratorPackage(), c.Parent(), c.Node(), "function literal segments will be named \"function literal\" by default", "declare a function instead to improve segment name generation") } } diff --git a/parser/gin_test.go b/integrations/nrgin/parsing_test.go similarity index 90% rename from parser/gin_test.go rename to integrations/nrgin/parsing_test.go index 1c800e31..28f51036 100644 --- a/parser/gin_test.go +++ b/integrations/nrgin/parsing_test.go @@ -1,4 +1,4 @@ -package parser +package nrgin_test import ( "go/ast" @@ -6,6 +6,10 @@ import ( "go/types" "testing" + "github.com/newrelic/go-easy-instrumentation/integrations/nragent" + "github.com/newrelic/go-easy-instrumentation/integrations/nrgin" + "github.com/newrelic/go-easy-instrumentation/parser" + "github.com/dave/dst" "github.com/dave/dst/decorator" "github.com/stretchr/testify/assert" @@ -106,8 +110,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentMain, InstrumentGinMiddleware) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nragent.InstrumentMain, nrgin.InstrumentGinMiddleware) assert.Equal(t, tt.expect, got) }) } @@ -132,7 +136,7 @@ func TestGinMiddlewareCall(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "Default", - Path: ginImportPath, + Path: nrgin.GinImportPath, }, }, }, @@ -152,7 +156,7 @@ func TestGinMiddlewareCall(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "New", - Path: ginImportPath, + Path: nrgin.GinImportPath, }, }, }, @@ -192,13 +196,13 @@ func TestGinMiddlewareCall(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "New", - Path: ginImportPath, + Path: nrgin.GinImportPath, }, }, &dst.CallExpr{ Fun: &dst.Ident{ Name: "Default", - Path: ginImportPath, + Path: nrgin.GinImportPath, }, }, }, @@ -208,8 +212,8 @@ func TestGinMiddlewareCall(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := ginMiddlewareCall(tt.stmt) + defer parser.PanicRecovery(t) + got := nrgin.GinMiddlewareCall(tt.stmt) assert.Equal(t, tt.want, got) }) } @@ -227,7 +231,7 @@ func TestGetGinHandlerContext(t *testing.T) { }, Type: &dst.Ident{ Name: "Context", - Path: ginImportPath, + Path: nrgin.GinImportPath, }, }, }, @@ -297,7 +301,7 @@ func TestGetGinHandlerContext(t *testing.T) { }, Type: &dst.Ident{ Name: "Context", - Path: ginImportPath, + Path: nrgin.GinImportPath, }, }, { @@ -306,7 +310,7 @@ func TestGetGinHandlerContext(t *testing.T) { }, Type: &dst.Ident{ Name: "Context", - Path: ginImportPath, + Path: nrgin.GinImportPath, }, }, }, @@ -324,7 +328,7 @@ func TestGetGinHandlerContext(t *testing.T) { Names: []*dst.Ident{}, Type: &dst.Ident{ Name: "Context", - Path: ginImportPath, + Path: nrgin.GinImportPath, }, }, }, @@ -337,7 +341,7 @@ func TestGetGinHandlerContext(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ok := getGinContextFromHandler(tt.node, tt.pkg) + ok := nrgin.GetGinContextFromHandler(tt.node, tt.pkg) if ok != tt.want { t.Errorf("expected %v, got %v", tt.want, ok) } @@ -406,7 +410,7 @@ func TestDefineTxnFromGinCtx(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defineTxnFromGinCtx(tt.body, tt.txnVariable, tt.ctxName) + nrgin.DefineTxnFromGinCtx(tt.body, tt.txnVariable, tt.ctxName) assert.Equal(t, tt.want, tt.body) }) } diff --git a/parser/gochi.go b/integrations/nrgochi/chi.go similarity index 70% rename from parser/gochi.go rename to integrations/nrgochi/chi.go index 84f6f1f7..72cde970 100644 --- a/parser/gochi.go +++ b/integrations/nrgochi/chi.go @@ -1,4 +1,4 @@ -package parser +package nrgochi import ( "fmt" @@ -10,11 +10,12 @@ import ( "github.com/dave/dst/dstutil" "github.com/newrelic/go-easy-instrumentation/internal/codegen" "github.com/newrelic/go-easy-instrumentation/internal/comment" + "github.com/newrelic/go-easy-instrumentation/parser" "github.com/newrelic/go-easy-instrumentation/parser/tracestate" ) const ( - gochiImportPath = "github.com/go-chi/chi/v5" + GochiImportPath = "github.com/go-chi/chi/v5" ) // Return the variable name of the Chi router object. @@ -22,7 +23,7 @@ const ( // // router := chi.NewRouter() // ^^^^^^ -func getChiRouterName(stmt dst.Stmt) string { +func GetChiRouterName(stmt dst.Stmt) string { // Verify we're dealing with an assignment operation v, ok := stmt.(*dst.AssignStmt) if !ok || len(v.Rhs) != 1 { @@ -46,7 +47,7 @@ func getChiRouterName(stmt dst.Stmt) string { } // Reject calls that are not to the `NewRouter` Fn. Verify Chi relationship with the import path. - if ident.Name != "NewRouter" || ident.Path != gochiImportPath { + if ident.Name != "NewRouter" || ident.Path != GochiImportPath { return "" } @@ -110,24 +111,24 @@ func getChiHTTPHandlerRouteName(callExpr *dst.CallExpr) (string, *dst.FuncLit) { // InstrumentChiMiddleware detects whether a Chi Router has been initialized // and adds New Relic Go Agent Middleware via the router.Use() method to // instrument the routes registered to the router. -func InstrumentChiMiddleware(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { - routerName := getChiRouterName(stmt) +func InstrumentChiMiddleware(manager *parser.InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { + routerName := GetChiRouterName(stmt) if routerName == "" { return false } // Append at the current stmt location - middleware, goGet := codegen.NrChiMiddleware(routerName, tracing.AgentVariable()) - comment.Debug(manager.getDecoratorPackage(), stmt, fmt.Sprintf("Injecting nrgochi middleware for router: %s", routerName)) + middleware, goGet := NrChiMiddleware(routerName, tracing.AgentVariable()) + comment.Debug(manager.GetDecoratorPackage(), stmt, fmt.Sprintf("Injecting nrgochi middleware for router: %s", routerName)) c.InsertAfter(middleware) - manager.addImport(goGet) + manager.AddImport(goGet) return true } // InstrumentChiRouterLiteral detects if a Chi Router route uses a function // literal and adds Txn/Segment tracing logic directly to the function literal // block. -func InstrumentChiRouterLiteral(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { +func InstrumentChiRouterLiteral(manager *parser.InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { methodName, callExpr := getChiHTTPMethod(c.Node()) if methodName == "" || callExpr == nil { return false @@ -151,9 +152,41 @@ func InstrumentChiRouterLiteral(manager *InstrumentationManager, stmt dst.Stmt, segmentName := methodName + ":" + routeName - comment.Debug(manager.getDecoratorPackage(), stmt, fmt.Sprintf("Injecting segment for Chi route: %s", segmentName)) + comment.Debug(manager.GetDecoratorPackage(), stmt, fmt.Sprintf("Injecting segment for Chi route: %s", segmentName)) codegen.PrependStatementToFunctionLit(fnLit, codegen.DeferSegment(segmentName, tracing.TransactionVariable())) codegen.PrependStatementToFunctionLit(fnLit, txn) return true } + +// getHTTPRequestArgName extracts the HTTP request argument name from a function literal. +// It returns (true, argName) if found, (false, "") otherwise. +func getHTTPRequestArgName(fnLit *dst.FuncLit) (bool, string) { + if fnLit == nil || fnLit.Type == nil || fnLit.Type.Params == nil { + return false, "" + } + + // Look for http.Request parameter + for _, field := range fnLit.Type.Params.List { + if len(field.Names) == 0 { + continue + } + + // Check if type is *http.Request + starExpr, ok := field.Type.(*dst.StarExpr) + if !ok { + continue + } + + ident, ok := starExpr.X.(*dst.Ident) + if !ok { + continue + } + + if ident.Name == "Request" && ident.Path == "net/http" { + return true, field.Names[0].Name + } + } + + return false, "" +} diff --git a/internal/codegen/gochi.go b/integrations/nrgochi/codegen.go similarity index 97% rename from internal/codegen/gochi.go rename to integrations/nrgochi/codegen.go index 5500b3a7..19ee948c 100644 --- a/internal/codegen/gochi.go +++ b/integrations/nrgochi/codegen.go @@ -1,4 +1,4 @@ -package codegen +package nrgochi import "github.com/dave/dst" diff --git a/internal/codegen/gochi_test.go b/integrations/nrgochi/codegen_test.go similarity index 72% rename from internal/codegen/gochi_test.go rename to integrations/nrgochi/codegen_test.go index 332837fa..d6c98c9a 100644 --- a/internal/codegen/gochi_test.go +++ b/integrations/nrgochi/codegen_test.go @@ -1,6 +1,7 @@ -package codegen +package nrgochi_test import ( + "github.com/newrelic/go-easy-instrumentation/integrations/nrgochi" "reflect" "testing" @@ -11,7 +12,7 @@ const ( ChiImportPath = "github.com/go-chi/chi/v5" ) -func Test_NrChiMiddleware(t *testing.T) { +func TestNrChiMiddleware(t *testing.T) { type args struct { call *dst.CallExpr routerName string @@ -53,7 +54,7 @@ func Test_NrChiMiddleware(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "Middleware", - Path: NrChiImportPath, + Path: nrgochi.NrChiImportPath, }, Args: []dst.Expr{ &dst.Ident{Name: "NewRelicApplication"}, @@ -67,12 +68,12 @@ func Test_NrChiMiddleware(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, imp := NrChiMiddleware(tt.args.routerName, tt.args.agentVariableName) + got, imp := nrgochi.NrChiMiddleware(tt.args.routerName, tt.args.agentVariableName) if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NrChiMiddleware() = %v, want %v", got, tt.want) + t.Errorf("nrgochi.NrChiMiddleware() = %v, want %v", got, tt.want) } - if imp != NrChiImportPath { - t.Errorf("NrChiMiddleware() = %v, want %v", imp, NrChiImportPath) + if imp != nrgochi.NrChiImportPath { + t.Errorf("nrgochi.NrChiMiddleware() = %v, want %v", imp, nrgochi.NrChiImportPath) } }) } diff --git a/end-to-end-tests/gochi/expect.ref b/integrations/nrgochi/example/gochi/expect.ref similarity index 100% rename from end-to-end-tests/gochi/expect.ref rename to integrations/nrgochi/example/gochi/expect.ref diff --git a/end-to-end-tests/gochi/go.mod b/integrations/nrgochi/example/gochi/go.mod similarity index 100% rename from end-to-end-tests/gochi/go.mod rename to integrations/nrgochi/example/gochi/go.mod diff --git a/end-to-end-tests/gochi/go.sum b/integrations/nrgochi/example/gochi/go.sum similarity index 100% rename from end-to-end-tests/gochi/go.sum rename to integrations/nrgochi/example/gochi/go.sum diff --git a/end-to-end-tests/gochi/main.go b/integrations/nrgochi/example/gochi/main.go similarity index 100% rename from end-to-end-tests/gochi/main.go rename to integrations/nrgochi/example/gochi/main.go diff --git a/parser/gochi_test.go b/integrations/nrgochi/parsing_test.go similarity index 87% rename from parser/gochi_test.go rename to integrations/nrgochi/parsing_test.go index dd98c709..67a8cc3c 100644 --- a/parser/gochi_test.go +++ b/integrations/nrgochi/parsing_test.go @@ -1,6 +1,9 @@ -package parser +package nrgochi_test import ( + "github.com/newrelic/go-easy-instrumentation/integrations/nragent" + "github.com/newrelic/go-easy-instrumentation/integrations/nrgochi" + "github.com/newrelic/go-easy-instrumentation/parser" "go/token" "testing" @@ -18,6 +21,9 @@ func TestInstrumentChiRouter(t *testing.T) { name: "detect and trace chi router in main function", code: `package main import ( + "github.com/newrelic/go-easy-instrumentation/integrations/nragent" + "github.com/newrelic/go-easy-instrumentation/integrations/nrgochi" + "github.com/newrelic/go-easy-instrumentation/parser" "net/http" chi "github.com/go-chi/chi/v5" @@ -108,8 +114,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentMain, InstrumentChiMiddleware) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nragent.InstrumentMain, nrgochi.InstrumentChiMiddleware) assert.Equal(t, tt.expect, got) }) } @@ -134,7 +140,7 @@ func TestChiMiddlewareCall(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewRouter", - Path: gochiImportPath, + Path: nrgochi.GochiImportPath, }, }, }, @@ -174,7 +180,7 @@ func TestChiMiddlewareCall(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "New", - Path: gochiImportPath, + Path: nrgochi.GochiImportPath, }, }, }, @@ -184,8 +190,8 @@ func TestChiMiddlewareCall(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := getChiRouterName(tt.stmt) + defer parser.PanicRecovery(t) + got := nrgochi.GetChiRouterName(tt.stmt) assert.Equal(t, tt.want, got) }) } @@ -321,8 +327,8 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentMain, InstrumentChiRouterLiteral) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nragent.InstrumentMain, nrgochi.InstrumentChiRouterLiteral) assert.Equal(t, tt.expect, got) }) } diff --git a/internal/codegen/grpc.go b/integrations/nrgrpc/codegen.go similarity index 91% rename from internal/codegen/grpc.go rename to integrations/nrgrpc/codegen.go index 548d1fa4..4bf50e2f 100644 --- a/internal/codegen/grpc.go +++ b/integrations/nrgrpc/codegen.go @@ -1,4 +1,4 @@ -package codegen +package nrgrpc import "github.com/dave/dst" @@ -8,7 +8,7 @@ const ( ) // This must be invoked on each argument added to a call expression to ensure the correct spacing rules are applied -func getCallExpressionArgumentSpacing(call *dst.CallExpr) dst.NodeDecs { +func GetCallExpressionArgumentSpacing(call *dst.CallExpr) dst.NodeDecs { // no standard has been set yet, we prefer to newline each new statement we add. // this will change the original decorator rules if len(call.Args) == 1 { @@ -34,7 +34,7 @@ func getCallExpressionArgumentSpacing(call *dst.CallExpr) dst.NodeDecs { // GrpcUnaryInterceptor generates a dst Call Expression for a newrelic nrgrpc unary interceptor func NrGrpcUnaryClientInterceptor(call *dst.CallExpr) *dst.CallExpr { - decs := getCallExpressionArgumentSpacing(call) + decs := GetCallExpressionArgumentSpacing(call) return &dst.CallExpr{ Fun: &dst.Ident{ Name: "WithUnaryInterceptor", @@ -53,7 +53,7 @@ func NrGrpcUnaryClientInterceptor(call *dst.CallExpr) *dst.CallExpr { } func NrGrpcStreamClientInterceptor(call *dst.CallExpr) *dst.CallExpr { - decs := getCallExpressionArgumentSpacing(call) + decs := GetCallExpressionArgumentSpacing(call) return &dst.CallExpr{ Fun: &dst.Ident{ Name: "WithStreamInterceptor", @@ -72,7 +72,7 @@ func NrGrpcStreamClientInterceptor(call *dst.CallExpr) *dst.CallExpr { } func NrGrpcUnaryServerInterceptor(agentVariable dst.Expr, call *dst.CallExpr) *dst.CallExpr { - decs := getCallExpressionArgumentSpacing(call) + decs := GetCallExpressionArgumentSpacing(call) return &dst.CallExpr{ Fun: &dst.Ident{ Name: "UnaryInterceptor", @@ -96,7 +96,7 @@ func NrGrpcUnaryServerInterceptor(agentVariable dst.Expr, call *dst.CallExpr) *d } func NrGrpcStreamServerInterceptor(agentVariable dst.Expr, call *dst.CallExpr) *dst.CallExpr { - decs := getCallExpressionArgumentSpacing(call) + decs := GetCallExpressionArgumentSpacing(call) return &dst.CallExpr{ Fun: &dst.Ident{ Name: "StreamInterceptor", diff --git a/internal/codegen/grpc_test.go b/integrations/nrgrpc/codegen_test.go similarity index 83% rename from internal/codegen/grpc_test.go rename to integrations/nrgrpc/codegen_test.go index 81b86b3f..28107a3c 100644 --- a/internal/codegen/grpc_test.go +++ b/integrations/nrgrpc/codegen_test.go @@ -1,6 +1,7 @@ -package codegen +package nrgrpc_test import ( + "github.com/newrelic/go-easy-instrumentation/integrations/nrgrpc" "go/token" "reflect" "testing" @@ -8,7 +9,7 @@ import ( "github.com/dave/dst" ) -func Test_getCallExpressionArgumentSpacing(t *testing.T) { +func TestGetCallExpressionArgumentSpacing(t *testing.T) { type args struct { call *dst.CallExpr } @@ -23,7 +24,7 @@ func Test_getCallExpressionArgumentSpacing(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{}, }, @@ -39,7 +40,7 @@ func Test_getCallExpressionArgumentSpacing(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{ &dst.BasicLit{ @@ -60,7 +61,7 @@ func Test_getCallExpressionArgumentSpacing(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{ &dst.BasicLit{ @@ -88,7 +89,7 @@ func Test_getCallExpressionArgumentSpacing(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{ &dst.BasicLit{ @@ -119,8 +120,8 @@ func Test_getCallExpressionArgumentSpacing(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := getCallExpressionArgumentSpacing(tt.args.call); !reflect.DeepEqual(got, tt.want) { - t.Errorf("getCallExpressionArgumentSpacing() = %v, want %v", got, tt.want) + if got := nrgrpc.GetCallExpressionArgumentSpacing(tt.args.call); !reflect.DeepEqual(got, tt.want) { + t.Errorf("nrgrpc.GetCallExpressionArgumentSpacing() = %v, want %v", got, tt.want) } if len(tt.args.call.Args) == 1 { if tt.args.call.Args[0].Decorations().After != dst.NewLine { @@ -146,11 +147,11 @@ func TestNrGrpcUnaryClientInterceptor(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "Dial", - Path: GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{}, }, - wantPath: GrpcImportPath, + wantPath: nrgrpc.GrpcImportPath, wantName: "WithUnaryInterceptor", }, { @@ -158,7 +159,7 @@ func TestNrGrpcUnaryClientInterceptor(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "Dial", - Path: GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{ &dst.BasicLit{ @@ -167,17 +168,17 @@ func TestNrGrpcUnaryClientInterceptor(t *testing.T) { }, }, }, - wantPath: GrpcImportPath, + wantPath: nrgrpc.GrpcImportPath, wantName: "WithUnaryInterceptor", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := NrGrpcUnaryClientInterceptor(tt.call) + got := nrgrpc.NrGrpcUnaryClientInterceptor(tt.call) if got == nil { - t.Fatal("NrGrpcUnaryClientInterceptor() returned nil") + t.Fatal("nrgrpc.NrGrpcUnaryClientInterceptor() returned nil") } // Check the function identifier @@ -203,8 +204,8 @@ func TestNrGrpcUnaryClientInterceptor(t *testing.T) { if argIdent.Name != "UnaryClientInterceptor" { t.Errorf("expected arg name %q, got %q", "UnaryClientInterceptor", argIdent.Name) } - if argIdent.Path != NrgrpcImportPath { - t.Errorf("expected arg path %q, got %q", NrgrpcImportPath, argIdent.Path) + if argIdent.Path != nrgrpc.NrgrpcImportPath { + t.Errorf("expected arg path %q, got %q", nrgrpc.NrgrpcImportPath, argIdent.Path) } }) } @@ -222,21 +223,21 @@ func TestNrGrpcStreamClientInterceptor(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "Dial", - Path: GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{}, }, - wantPath: GrpcImportPath, + wantPath: nrgrpc.GrpcImportPath, wantName: "WithStreamInterceptor", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := NrGrpcStreamClientInterceptor(tt.call) + got := nrgrpc.NrGrpcStreamClientInterceptor(tt.call) if got == nil { - t.Fatal("NrGrpcStreamClientInterceptor() returned nil") + t.Fatal("nrgrpc.NrGrpcStreamClientInterceptor() returned nil") } // Check the function identifier @@ -262,8 +263,8 @@ func TestNrGrpcStreamClientInterceptor(t *testing.T) { if argIdent.Name != "StreamClientInterceptor" { t.Errorf("expected arg name %q, got %q", "StreamClientInterceptor", argIdent.Name) } - if argIdent.Path != NrgrpcImportPath { - t.Errorf("expected arg path %q, got %q", NrgrpcImportPath, argIdent.Path) + if argIdent.Path != nrgrpc.NrgrpcImportPath { + t.Errorf("expected arg path %q, got %q", nrgrpc.NrgrpcImportPath, argIdent.Path) } }) } @@ -283,21 +284,21 @@ func TestNrGrpcUnaryServerInterceptor(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{}, }, - wantPath: GrpcImportPath, + wantPath: nrgrpc.GrpcImportPath, wantName: "UnaryInterceptor", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := NrGrpcUnaryServerInterceptor(tt.agentVariable, tt.call) + got := nrgrpc.NrGrpcUnaryServerInterceptor(tt.agentVariable, tt.call) if got == nil { - t.Fatal("NrGrpcUnaryServerInterceptor() returned nil") + t.Fatal("nrgrpc.NrGrpcUnaryServerInterceptor() returned nil") } // Check the function identifier @@ -329,8 +330,8 @@ func TestNrGrpcUnaryServerInterceptor(t *testing.T) { if innerFun.Name != "UnaryServerInterceptor" { t.Errorf("expected inner function name %q, got %q", "UnaryServerInterceptor", innerFun.Name) } - if innerFun.Path != NrgrpcImportPath { - t.Errorf("expected inner function path %q, got %q", NrgrpcImportPath, innerFun.Path) + if innerFun.Path != nrgrpc.NrgrpcImportPath { + t.Errorf("expected inner function path %q, got %q", nrgrpc.NrgrpcImportPath, innerFun.Path) } // Check the agent variable is passed @@ -355,21 +356,21 @@ func TestNrGrpcStreamServerInterceptor(t *testing.T) { call: &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{}, }, - wantPath: GrpcImportPath, + wantPath: nrgrpc.GrpcImportPath, wantName: "StreamInterceptor", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := NrGrpcStreamServerInterceptor(tt.agentVariable, tt.call) + got := nrgrpc.NrGrpcStreamServerInterceptor(tt.agentVariable, tt.call) if got == nil { - t.Fatal("NrGrpcStreamServerInterceptor() returned nil") + t.Fatal("nrgrpc.NrGrpcStreamServerInterceptor() returned nil") } // Check the function identifier @@ -401,8 +402,8 @@ func TestNrGrpcStreamServerInterceptor(t *testing.T) { if innerFun.Name != "StreamServerInterceptor" { t.Errorf("expected inner function name %q, got %q", "StreamServerInterceptor", innerFun.Name) } - if innerFun.Path != NrgrpcImportPath { - t.Errorf("expected inner function path %q, got %q", NrgrpcImportPath, innerFun.Path) + if innerFun.Path != nrgrpc.NrgrpcImportPath { + t.Errorf("expected inner function path %q, got %q", nrgrpc.NrgrpcImportPath, innerFun.Path) } // Check the agent variable is passed @@ -430,10 +431,10 @@ func TestGrpcStreamContext(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := GrpcStreamContext(tt.streamServerObject) + got := nrgrpc.GrpcStreamContext(tt.streamServerObject) if got == nil { - t.Fatal("GrpcStreamContext() returned nil") + t.Fatal("nrgrpc.GrpcStreamContext() returned nil") } // Check it's a selector expression diff --git a/end-to-end-tests/grpc/client/client.go b/integrations/nrgrpc/example/grpc/client/client.go similarity index 100% rename from end-to-end-tests/grpc/client/client.go rename to integrations/nrgrpc/example/grpc/client/client.go diff --git a/end-to-end-tests/grpc/expect.ref b/integrations/nrgrpc/example/grpc/expect.ref similarity index 100% rename from end-to-end-tests/grpc/expect.ref rename to integrations/nrgrpc/example/grpc/expect.ref diff --git a/end-to-end-tests/grpc/go.mod b/integrations/nrgrpc/example/grpc/go.mod similarity index 100% rename from end-to-end-tests/grpc/go.mod rename to integrations/nrgrpc/example/grpc/go.mod diff --git a/end-to-end-tests/grpc/go.sum b/integrations/nrgrpc/example/grpc/go.sum similarity index 100% rename from end-to-end-tests/grpc/go.sum rename to integrations/nrgrpc/example/grpc/go.sum diff --git a/end-to-end-tests/grpc/sampleapp/sampleapp.pb.go b/integrations/nrgrpc/example/grpc/sampleapp/sampleapp.pb.go similarity index 100% rename from end-to-end-tests/grpc/sampleapp/sampleapp.pb.go rename to integrations/nrgrpc/example/grpc/sampleapp/sampleapp.pb.go diff --git a/end-to-end-tests/grpc/sampleapp/sampleapp.proto b/integrations/nrgrpc/example/grpc/sampleapp/sampleapp.proto similarity index 100% rename from end-to-end-tests/grpc/sampleapp/sampleapp.proto rename to integrations/nrgrpc/example/grpc/sampleapp/sampleapp.proto diff --git a/end-to-end-tests/grpc/server/server.go b/integrations/nrgrpc/example/grpc/server/server.go similarity index 100% rename from end-to-end-tests/grpc/server/server.go rename to integrations/nrgrpc/example/grpc/server/server.go diff --git a/parser/grpc.go b/integrations/nrgrpc/grpc.go similarity index 66% rename from parser/grpc.go rename to integrations/nrgrpc/grpc.go index 92dc0c1e..c663a6fa 100644 --- a/parser/grpc.go +++ b/integrations/nrgrpc/grpc.go @@ -1,4 +1,4 @@ -package parser +package nrgrpc import ( "fmt" @@ -9,6 +9,7 @@ import ( "github.com/dave/dst/decorator" "github.com/dave/dst/dstutil" "github.com/newrelic/go-easy-instrumentation/internal/codegen" + "github.com/newrelic/go-easy-instrumentation/parser" "github.com/newrelic/go-easy-instrumentation/internal/comment" "github.com/newrelic/go-easy-instrumentation/internal/util" "github.com/newrelic/go-easy-instrumentation/parser/facts" @@ -18,18 +19,18 @@ import ( const ( grpcServerType = "*google.golang.org/grpc.Server" - grpcServerStreamType = "google.golang.org/grpc.ServerStream" + GrpcServerStreamType = "google.golang.org/grpc.ServerStream" grpcPath = "google.golang.org/grpc" contextType = "context.Context" ) -func grpcDialCall(node dst.Node) (*dst.CallExpr, bool) { +func GrpcDialCall(node dst.Node) (*dst.CallExpr, bool) { switch v := node.(type) { case *dst.AssignStmt: if len(v.Rhs) == 1 { if call, ok := v.Rhs[0].(*dst.CallExpr); ok { if ident, ok := call.Fun.(*dst.Ident); ok { - if ident.Name == "Dial" && ident.Path == codegen.GrpcImportPath { + if ident.Name == "Dial" && ident.Path == GrpcImportPath { return call, true } } @@ -38,7 +39,7 @@ func grpcDialCall(node dst.Node) (*dst.CallExpr, bool) { case *dst.ExprStmt: if call, ok := v.X.(*dst.CallExpr); ok { if ident, ok := call.Fun.(*dst.Ident); ok { - if ident.Name == "Dial" && ident.Path == codegen.GrpcImportPath { + if ident.Name == "Dial" && ident.Path == GrpcImportPath { return call, true } } @@ -47,13 +48,13 @@ func grpcDialCall(node dst.Node) (*dst.CallExpr, bool) { return nil, false } -func grpcNewServerCall(node dst.Node) (*dst.CallExpr, bool) { +func GrpcNewServerCall(node dst.Node) (*dst.CallExpr, bool) { switch v := node.(type) { case *dst.AssignStmt: if len(v.Rhs) == 1 { if call, ok := v.Rhs[0].(*dst.CallExpr); ok { if ident, ok := call.Fun.(*dst.Ident); ok { - if ident.Name == "NewServer" && ident.Path == codegen.GrpcImportPath { + if ident.Name == "NewServer" && ident.Path == GrpcImportPath { return call, true } } @@ -62,7 +63,7 @@ func grpcNewServerCall(node dst.Node) (*dst.CallExpr, bool) { case *dst.ExprStmt: if call, ok := v.X.(*dst.CallExpr); ok { if ident, ok := call.Fun.(*dst.Ident); ok { - if ident.Name == "NewServer" && ident.Path == codegen.GrpcImportPath { + if ident.Name == "NewServer" && ident.Path == GrpcImportPath { return call, true } } @@ -74,21 +75,21 @@ func grpcNewServerCall(node dst.Node) (*dst.CallExpr, bool) { // Stateless Tracing Functions // //////////////////////////////////////////// -// traceObject must not be nil if grpcServerTxnData is returned -type grpcServerTxnData struct { - assignment *dst.AssignStmt - traceObject traceobject.TraceObject +// TraceObject must not be nil if GrpcServerTxnData is returned +type GrpcServerTxnData struct { + TxnAssignment *dst.AssignStmt + TraceObject traceobject.TraceObject } // getTxnFromGrpcServer finds the transaction object from a gRPC server method // This is done by looking for a context object or a stream server object in the function parameters // and then pulling the transaction from that object and assigning it to a variable. -func getTxnFromGrpcServer(manager *InstrumentationManager, params []*dst.Field, txnVariableName string) (*grpcServerTxnData, bool) { +func GetTxnFromGrpcServer(manager *parser.InstrumentationManager, params []*dst.Field, txnVariableName string) (*GrpcServerTxnData, bool) { // Find stream server object parameters first var streamServerIdent *dst.Ident var contextIdent *dst.Ident - pkg := manager.getDecoratorPackage() + pkg := manager.GetDecoratorPackage() for _, param := range params { paramType := util.TypeOf(param.Names[0], pkg) if paramType != nil { @@ -96,7 +97,7 @@ func getTxnFromGrpcServer(manager *InstrumentationManager, params []*dst.Field, if len(param.Names) == 1 { // check if this is a stream server object or a context object paramTypeName := paramType.String() - if util.IsUnderlyingType(underlyingType, grpcServerStreamType) { + if util.IsUnderlyingType(underlyingType, GrpcServerStreamType) { streamServerIdent = param.Names[0] } else if paramTypeName == contextType { contextIdent = param.Names[0] @@ -105,33 +106,33 @@ func getTxnFromGrpcServer(manager *InstrumentationManager, params []*dst.Field, } } - var txnData *grpcServerTxnData + var txnData *GrpcServerTxnData var ok bool if streamServerIdent != nil { ok = true - txnData = &grpcServerTxnData{ - assignment: codegen.TxnFromContext(txnVariableName, codegen.GrpcStreamContext(streamServerIdent)), - traceObject: traceobject.NewTransaction(), + txnData = &GrpcServerTxnData{ + TxnAssignment: codegen.TxnFromContext(txnVariableName, GrpcStreamContext(streamServerIdent)), + TraceObject: traceobject.NewTransaction(), } } else if contextIdent != nil { ok = true - txnData = &grpcServerTxnData{ - traceObject: traceobject.NewContext(contextIdent.Name), + txnData = &GrpcServerTxnData{ + TraceObject: traceobject.NewContext(contextIdent.Name), } } return txnData, ok } -// isGrpcServerMethod checks if a function declaration is a method of the user's gRPC server +// IsGrpcServerMethod checks if a function declaration is a method of the user's gRPC server // based on facts generated from scanning their gRPC configuration code. -func isGrpcServerMethod(manager *InstrumentationManager, funcDecl *dst.FuncDecl) bool { +func IsGrpcServerMethod(manager *parser.InstrumentationManager, funcDecl *dst.FuncDecl) bool { if funcDecl.Recv == nil || len(funcDecl.Recv.List) != 1 || len(funcDecl.Recv.List[0].Names) != 1 { return false } // attempt to get the type of the receiver - pkg := manager.getDecoratorPackage() + pkg := manager.GetDecoratorPackage() recvType := util.TypeOf(funcDecl.Recv.List[0].Names[0], pkg) if recvType == nil { return false @@ -139,43 +140,43 @@ func isGrpcServerMethod(manager *InstrumentationManager, funcDecl *dst.FuncDecl) // check if the receiver is a gRPC server method using the FactStore recvTypeString := recvType.String() - fact := manager.facts.GetFact(recvTypeString) + fact := manager.Facts().GetFact(recvTypeString) return fact == facts.GrpcServerType } // InstrumentGrpcServerMethod finds methods of a declared gRPC server and pulls tracing through it -func InstrumentGrpcServerMethod(manager *InstrumentationManager, c *dstutil.Cursor) { +func InstrumentGrpcServerMethod(manager *parser.InstrumentationManager, c *dstutil.Cursor) { n := c.Node() funcDecl, ok := n.(*dst.FuncDecl) - if !ok || !isGrpcServerMethod(manager, funcDecl) { + if !ok || !IsGrpcServerMethod(manager, funcDecl) { return } // find either a context or a server stream object - txnData, ok := getTxnFromGrpcServer(manager, funcDecl.Type.Params.List, codegen.DefaultTransactionVariable) + txnData, ok := GetTxnFromGrpcServer(manager, funcDecl.Type.Params.List, codegen.DefaultTransactionVariable) if !ok { return } // ok is true if the body of this function has any tracing code added to it. If this is true, we know it needs a transaction to get // pulled from the grpc server object - node, ok := TraceFunction(manager, funcDecl, tracestate.FunctionBody(codegen.DefaultTransactionVariable, txnData.traceObject)) + node, ok := parser.TraceFunction(manager, funcDecl, tracestate.FunctionBody(codegen.DefaultTransactionVariable, txnData.TraceObject)) decl := node.(*dst.FuncDecl) - if ok && txnData.assignment != nil { - comment.Debug(manager.getDecoratorPackage(), funcDecl, fmt.Sprintf("Instrumenting gRPC server method: %s", funcDecl.Name.Name)) - decl.Body.List = append([]dst.Stmt{txnData.assignment}, decl.Body.List...) + if ok && txnData.TxnAssignment != nil { + comment.Debug(manager.GetDecoratorPackage(), funcDecl, fmt.Sprintf("Instrumenting gRPC server method: %s", funcDecl.Name.Name)) + decl.Body.List = append([]dst.Stmt{txnData.TxnAssignment}, decl.Body.List...) } } // InstrumentGrpcDial adds the New Relic gRPC client interceptor to the grpc.Dial client call // This function does not need any tracing context to work, nor will it produce any tracing context -func InstrumentGrpcDial(manager *InstrumentationManager, c *dstutil.Cursor) { +func InstrumentGrpcDial(manager *parser.InstrumentationManager, c *dstutil.Cursor) { currentNode := c.Node() - if callExpr, ok := grpcDialCall(currentNode); ok { - comment.Debug(manager.getDecoratorPackage(), currentNode, "Injecting gRPC client interceptors into grpc.Dial") - callExpr.Args = append(callExpr.Args, codegen.NrGrpcUnaryClientInterceptor(callExpr)) - callExpr.Args = append(callExpr.Args, codegen.NrGrpcStreamClientInterceptor(callExpr)) - manager.addImport(codegen.NrgrpcImportPath) + if callExpr, ok := GrpcDialCall(currentNode); ok { + comment.Debug(manager.GetDecoratorPackage(), currentNode, "Injecting gRPC client interceptors into grpc.Dial") + callExpr.Args = append(callExpr.Args, NrGrpcUnaryClientInterceptor(callExpr)) + callExpr.Args = append(callExpr.Args, NrGrpcStreamClientInterceptor(callExpr)) + manager.AddImport(NrgrpcImportPath) } } @@ -183,18 +184,18 @@ func InstrumentGrpcDial(manager *InstrumentationManager, c *dstutil.Cursor) { ////////////////////////////////////////////// // InstrumentGrpcServer adds the New Relic gRPC server interceptors to the grpc.NewServer call -func InstrumentGrpcServer(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { +func InstrumentGrpcServer(manager *parser.InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { // determine if this is a gRPC server initialization - callExpr, ok := grpcNewServerCall(stmt) + callExpr, ok := GrpcNewServerCall(stmt) if !ok { return false } // inject middleware - comment.Debug(manager.getDecoratorPackage(), stmt, "Injecting gRPC server interceptors into grpc.NewServer") - callExpr.Args = append(callExpr.Args, codegen.NrGrpcUnaryServerInterceptor(tracing.AgentVariable(), callExpr)) - callExpr.Args = append(callExpr.Args, codegen.NrGrpcStreamServerInterceptor(tracing.AgentVariable(), callExpr)) - manager.addImport(codegen.NrgrpcImportPath) + comment.Debug(manager.GetDecoratorPackage(), stmt, "Injecting gRPC server interceptors into grpc.NewServer") + callExpr.Args = append(callExpr.Args, NrGrpcUnaryServerInterceptor(tracing.AgentVariable(), callExpr)) + callExpr.Args = append(callExpr.Args, NrGrpcStreamServerInterceptor(tracing.AgentVariable(), callExpr)) + manager.AddImport(NrgrpcImportPath) return true } @@ -203,7 +204,7 @@ func InstrumentGrpcServer(manager *InstrumentationManager, stmt dst.Stmt, c *dst // isGrpcRegisterServerCall checks if a call expression is a call to a gRPC Register***Server function // must check length of call.Args == 2 before calling this. -func isGrpcRegisterServerCall(call *dst.CallExpr, pkg *decorator.Package) bool { +func IsGrpcRegisterServerCall(call *dst.CallExpr, pkg *decorator.Package) bool { if len(call.Args) != 2 { return false } @@ -219,7 +220,7 @@ func isGrpcRegisterServerCall(call *dst.CallExpr, pkg *decorator.Package) bool { } // Must be called on a call with 2 arguments -func getRegisteredServerIdent(call *dst.CallExpr) (*dst.Ident, bool) { +func GetRegisteredServerIdent(call *dst.CallExpr) (*dst.Ident, bool) { switch v := call.Args[1].(type) { case *dst.Ident: return v, true @@ -248,12 +249,12 @@ func FindGrpcServerObject(pkg *decorator.Package, node dst.Node) (facts.Entry, b // look for gRPC server registration call call, ok := expr.X.(*dst.CallExpr) - if !ok || !isGrpcRegisterServerCall(call, pkg) { + if !ok || !IsGrpcRegisterServerCall(call, pkg) { return facts.Entry{}, false } // get the server object that was registered - serverHandlerIdent, ok := getRegisteredServerIdent(call) + serverHandlerIdent, ok := GetRegisteredServerIdent(call) if !ok { return facts.Entry{}, false } diff --git a/parser/grpc_test.go b/integrations/nrgrpc/parsing_test.go similarity index 61% rename from parser/grpc_test.go rename to integrations/nrgrpc/parsing_test.go index ef6e5530..4deb2e1c 100644 --- a/parser/grpc_test.go +++ b/integrations/nrgrpc/parsing_test.go @@ -1,4 +1,4 @@ -package parser +package nrgrpc_test import ( "go/ast" @@ -7,11 +7,12 @@ import ( "reflect" "testing" + "github.com/newrelic/go-easy-instrumentation/integrations/nrgrpc" + "github.com/newrelic/go-easy-instrumentation/parser" + "github.com/dave/dst" "github.com/dave/dst/decorator" - "github.com/newrelic/go-easy-instrumentation/internal/codegen" "github.com/newrelic/go-easy-instrumentation/parser/facts" - "github.com/newrelic/go-easy-instrumentation/parser/tracestate/traceobject" "github.com/stretchr/testify/assert" "golang.org/x/tools/go/packages" ) @@ -63,8 +64,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentGrpcDial) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nrgrpc.InstrumentGrpcDial) assert.Equal(t, tt.expect, got) }) } @@ -108,14 +109,14 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatefulTracingFunction(t, tt.code, InstrumentGrpcServer, false) + defer parser.PanicRecovery(t) + got := parser.RunStatefulTracingFunction(t, tt.code, nrgrpc.InstrumentGrpcServer, false) assert.Equal(t, tt.expect, got) }) } } -func Test_grpcDialCall(t *testing.T) { +func TestGrpcDialCall(t *testing.T) { type args struct { node dst.Node } @@ -133,7 +134,7 @@ func Test_grpcDialCall(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "Dial", - Path: codegen.GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{ &dst.BasicLit{ @@ -156,7 +157,7 @@ func Test_grpcDialCall(t *testing.T) { want: &dst.CallExpr{ Fun: &dst.Ident{ Name: "Dial", - Path: codegen.GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{ &dst.BasicLit{ @@ -174,7 +175,7 @@ func Test_grpcDialCall(t *testing.T) { X: &dst.CallExpr{ Fun: &dst.Ident{ Name: "Dial", - Path: codegen.GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{ &dst.BasicLit{ @@ -188,7 +189,7 @@ func Test_grpcDialCall(t *testing.T) { want: &dst.CallExpr{ Fun: &dst.Ident{ Name: "Dial", - Path: codegen.GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{ &dst.BasicLit{ @@ -254,18 +255,18 @@ func Test_grpcDialCall(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1 := grpcDialCall(tt.args.node) + got, got1 := nrgrpc.GrpcDialCall(tt.args.node) if !reflect.DeepEqual(got, tt.want) { - t.Errorf("grpcDialCall() got = %v, want %v", got, tt.want) + t.Errorf("nrgrpc.GrpcDialCall() got = %v, want %v", got, tt.want) } if got1 != tt.want1 { - t.Errorf("grpcDialCall() got1 = %v, want %v", got1, tt.want1) + t.Errorf("nrgrpc.GrpcDialCall() got1 = %v, want %v", got1, tt.want1) } }) } } -func Test_grpcNewServerCall(t *testing.T) { +func TestGrpcNewServerCall(t *testing.T) { type args struct { node dst.Node } @@ -283,7 +284,7 @@ func Test_grpcNewServerCall(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: codegen.GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{}, }, @@ -296,7 +297,7 @@ func Test_grpcNewServerCall(t *testing.T) { want: &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: codegen.GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{}, }, @@ -309,7 +310,7 @@ func Test_grpcNewServerCall(t *testing.T) { X: &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: codegen.GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{}, }, @@ -318,7 +319,7 @@ func Test_grpcNewServerCall(t *testing.T) { want: &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewServer", - Path: codegen.GrpcImportPath, + Path: nrgrpc.GrpcImportPath, }, Args: []dst.Expr{}, }, @@ -364,18 +365,18 @@ func Test_grpcNewServerCall(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1 := grpcNewServerCall(tt.args.node) + got, got1 := nrgrpc.GrpcNewServerCall(tt.args.node) if !reflect.DeepEqual(got, tt.want) { - t.Errorf("grpcNewServerCall() got = %v, want %v", got, tt.want) + t.Errorf("nrgrpc.GrpcNewServerCall() got = %v, want %v", got, tt.want) } if got1 != tt.want1 { - t.Errorf("grpcNewServerCall() got1 = %v, want %v", got1, tt.want1) + t.Errorf("nrgrpc.GrpcNewServerCall() got1 = %v, want %v", got1, tt.want1) } }) } } -func Test_isGrpcRegisterServerCall(t *testing.T) { +func TestIsGrpcRegisterServerCall(t *testing.T) { serverArg := &dst.Ident{ Name: "grpcServer", } @@ -412,7 +413,7 @@ func Test_isGrpcRegisterServerCall(t *testing.T) { }, } - ok := isGrpcRegisterServerCall(functionCallExpr, pkg) + ok := nrgrpc.IsGrpcRegisterServerCall(functionCallExpr, pkg) if !ok { t.Error("expected valid server to return true") } @@ -422,7 +423,7 @@ func Test_isGrpcRegisterServerCall(t *testing.T) { Name: "RegisterTestFooBarServer", Path: "testGrpcPackage", } - ok = isGrpcRegisterServerCall(functionCallExpr, pkg) + ok = nrgrpc.IsGrpcRegisterServerCall(functionCallExpr, pkg) if !ok { t.Error("expected valid server to return true") } @@ -432,14 +433,14 @@ func Test_isGrpcRegisterServerCall(t *testing.T) { Name: "RegisterTestService", Path: "testGrpcPackage", } - ok = isGrpcRegisterServerCall(functionCallExpr, pkg) + ok = nrgrpc.IsGrpcRegisterServerCall(functionCallExpr, pkg) if ok { t.Error("expected invalid call to return false") } } -func Test_getRegisteredServerIdent(t *testing.T) { +func TestGetRegisteredServerIdent(t *testing.T) { type args struct { call *dst.CallExpr } @@ -487,7 +488,7 @@ func Test_getRegisteredServerIdent(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, ok := getRegisteredServerIdent(tt.args.call) + got, ok := nrgrpc.GetRegisteredServerIdent(tt.args.call) if ok && tt.expect { assert.Equal(t, tt.want, got) } @@ -545,7 +546,7 @@ func TestFindGrpcServerObject(t *testing.T) { }, } - fact, ok := FindGrpcServerObject(pkg, functionCallExpr) + fact, ok := nrgrpc.FindGrpcServerObject(pkg, functionCallExpr) if !ok { t.Error("expected valid server to return true") } else { @@ -558,228 +559,3 @@ func TestFindGrpcServerObject(t *testing.T) { } } -func TestGetTxnFromGrpcServer(t *testing.T) { - grpcServerStreamType := types.NewNamed( - types.NewTypeName(0, nil, "mainType", nil), // Main Type - types.NewInterfaceType( // Underlying Type - nil, - []types.Type{ - types.NewNamed( - types.NewTypeName(0, nil, grpcServerStreamType, nil), - nil, - nil, - ), - }, - ), - nil, - ) - - contextParamName := &dst.Ident{Name: "ctx"} - astContext := &ast.Ident{Name: "ctx"} - serverStreamParamName := &dst.Ident{Name: "stream"} - astServerStream := &ast.Ident{Name: "stream"} - manager := &InstrumentationManager{ - currentPackage: "test", - packages: map[string]*packageState{ - "test": { - pkg: &decorator.Package{ - Package: &packages.Package{ - TypesInfo: &types.Info{ - Types: map[ast.Expr]types.TypeAndValue{ - astContext: { - Type: types.NewNamed(types.NewTypeName(token.NoPos, types.NewPackage("context", "context"), "Context", nil), nil, nil), - }, - astServerStream: { - Type: grpcServerStreamType, - }, - }, - }, - }, - Decorator: &decorator.Decorator{ - Map: decorator.Map{ - Ast: decorator.AstMap{ - Nodes: map[dst.Node]ast.Node{contextParamName: astContext, serverStreamParamName: astServerStream}, - }, - }, - }, - }, - }, - }, - facts: facts.Keeper{ - "github.com/example/testapp.TestApp_StreamServer": facts.GrpcServerStream, - }, - } - type args struct { - manager *InstrumentationManager - params []*dst.Field - } - tests := []struct { - name string - args - want *grpcServerTxnData - expect bool - }{ - { - name: "grpc server stream", - args: args{ - manager: manager, - params: []*dst.Field{ - { - Names: []*dst.Ident{serverStreamParamName}, - }, - }, - }, - want: &grpcServerTxnData{ - codegen.TxnFromContext("txn", codegen.GrpcStreamContext(serverStreamParamName)), - traceobject.NewTransaction(), - }, - expect: true, - }, - { - name: "grpc context", - args: args{ - manager: manager, - params: []*dst.Field{ - { - Names: []*dst.Ident{contextParamName}, - }, - }, - }, - want: &grpcServerTxnData{ - traceObject: traceobject.NewContext(contextParamName.Name), - }, - expect: true, - }, - { - name: "empty params", - args: args{ - manager: manager, - params: []*dst.Field{}, - }, - want: nil, - expect: false, - }, - { - name: "no context or stream", - args: args{ - manager: manager, - params: []*dst.Field{ - { - Names: []*dst.Ident{ - {Name: "notContext"}, - }, - }, - }, - }, - want: nil, - expect: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, ok := getTxnFromGrpcServer(tt.args.manager, tt.args.params, "txn") - if tt.expect { - if !ok { - t.Error("expected a transaction to be gotten from grpc server agrument") - } else { - assert.Equal(t, tt.want, got) - } - } else { - if ok { - t.Errorf("expected no transaction to be gotten from grpc server agrument, but got %+v", got) - } - } - }) - } -} -func TestIsGrpcServerMethod(t *testing.T) { - serverRecv := &dst.Ident{ - Name: "srv", - } - astServer := &ast.Ident{ - Name: "srv", - } - - manager := &InstrumentationManager{ - currentPackage: "test", - packages: map[string]*packageState{ - "test": { - pkg: &decorator.Package{ - Package: &packages.Package{ - TypesInfo: &types.Info{ - Types: map[ast.Expr]types.TypeAndValue{ - astServer: { - Type: types.NewPointer(types.NewNamed(types.NewTypeName(token.NoPos, types.NewPackage("github.com/example/testapp", "testapp"), "Server", types.NewInterfaceType(nil, nil)), nil, nil)), - }, - }, - }, - }, - Decorator: &decorator.Decorator{ - Map: decorator.Map{ - Ast: decorator.AstMap{ - Nodes: map[dst.Node]ast.Node{serverRecv: astServer}, - }, - }, - }, - }, - }, - }, - facts: facts.Keeper{ - "*github.com/example/testapp.Server": facts.GrpcServerType, - }, - } - - type args struct { - manager *InstrumentationManager - decl *dst.FuncDecl - } - tests := []struct { - name string - args - want bool - }{ - { - name: "grpc server method", - args: args{ - manager: manager, - decl: &dst.FuncDecl{ - Recv: &dst.FieldList{ - List: []*dst.Field{ - { - Names: []*dst.Ident{serverRecv}, - }, - }, - }, - }, - }, - want: true, - }, - { - name: "reciever is not grpc server", - args: args{ - manager: manager, - decl: &dst.FuncDecl{ - Recv: &dst.FieldList{ - List: []*dst.Field{ - { - Names: []*dst.Ident{ - { - Name: "notServer", - }, - }, - }, - }, - }, - }, - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := isGrpcServerMethod(tt.args.manager, tt.args.decl); got != tt.want { - t.Errorf("isGrpcServerMethod() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/codegen/mysql.go b/integrations/nrmysql/codegen.go similarity index 99% rename from internal/codegen/mysql.go rename to integrations/nrmysql/codegen.go index 605c1107..29c80ee8 100644 --- a/internal/codegen/mysql.go +++ b/integrations/nrmysql/codegen.go @@ -1,4 +1,4 @@ -package codegen +package nrmysql import ( "fmt" diff --git a/internal/codegen/mysql_test.go b/integrations/nrmysql/codegen_test.go similarity index 94% rename from internal/codegen/mysql_test.go rename to integrations/nrmysql/codegen_test.go index 5afd03ca..ba311efc 100644 --- a/internal/codegen/mysql_test.go +++ b/integrations/nrmysql/codegen_test.go @@ -1,6 +1,7 @@ -package codegen +package nrmysql_test import ( + "github.com/newrelic/go-easy-instrumentation/integrations/nrmysql" "go/token" "testing" @@ -45,7 +46,7 @@ func TestCreateSQLTransaction(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := CreateSQLTransaction(tt.agentVarName, tt.txnVarName, tt.sqlMethodName) + got := nrmysql.CreateSQLTransaction(tt.agentVarName, tt.txnVarName, tt.sqlMethodName) // Check it's an assignment statement with DEFINE token assert.NotNil(t, got) @@ -107,7 +108,7 @@ func TestCreateContextWithTransaction(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := CreateContextWithTransaction(tt.ctxName, tt.txnName) + got := nrmysql.CreateContextWithTransaction(tt.ctxName, tt.txnName) // Check it's an assignment statement with DEFINE token assert.NotNil(t, got) @@ -176,7 +177,7 @@ func TestCreateTransactionEnd(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := CreateTransactionEnd(tt.txnName) + got := nrmysql.CreateTransactionEnd(tt.txnName) // Check it's an expression statement assert.NotNil(t, got) diff --git a/integrations/nrmysql/example/mysql/expect.ref b/integrations/nrmysql/example/mysql/expect.ref new file mode 100644 index 00000000..cddcd8f0 --- /dev/null +++ b/integrations/nrmysql/example/mysql/expect.ref @@ -0,0 +1,29 @@ +--- a/main.go ++++ b/main.go +@@ -6,13 +6,19 @@ + import ( + "database/sql" + "fmt" ++ "time" + + _ "github.com/go-sql-driver/mysql" ++ "github.com/newrelic/go-agent/v3/newrelic" + ) + + func main() { + // Set up a local mysql docker container with: + // docker run -it -p 3306:3306 --net "bridge" -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql ++ NewRelicAgent, agentInitError := newrelic.NewApplication(newrelic.ConfigFromEnvironment()) ++ if agentInitError != nil { ++ panic(agentInitError) ++ } + + db, err := sql.Open("mysql", "root@/information_schema") + if err != nil { +@@ -24,4 +23,6 @@ + row.Scan(&count) + + fmt.Println("number of tables in information_schema", count) ++ ++ NewRelicAgent.Shutdown(5 * time.Second) + } diff --git a/integrations/nrmysql/example/mysql/go.mod b/integrations/nrmysql/example/mysql/go.mod new file mode 100644 index 00000000..5be23568 --- /dev/null +++ b/integrations/nrmysql/example/mysql/go.mod @@ -0,0 +1,5 @@ +module mysql + +go 1.24.0 + +require github.com/go-sql-driver/mysql v1.6.0 diff --git a/integrations/nrmysql/example/mysql/go.sum b/integrations/nrmysql/example/mysql/go.sum new file mode 100644 index 00000000..20c16d6f --- /dev/null +++ b/integrations/nrmysql/example/mysql/go.sum @@ -0,0 +1,2 @@ +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= diff --git a/end-to-end-tests/mysql/main.go b/integrations/nrmysql/example/mysql/main.go similarity index 51% rename from end-to-end-tests/mysql/main.go rename to integrations/nrmysql/example/mysql/main.go index e87ee259..22e0aeda 100644 --- a/end-to-end-tests/mysql/main.go +++ b/integrations/nrmysql/example/mysql/main.go @@ -6,36 +6,22 @@ package main import ( "database/sql" "fmt" - "os" - "time" - _ "github.com/newrelic/go-agent/v3/integrations/nrmysql" - "github.com/newrelic/go-agent/v3/newrelic" + _ "github.com/go-sql-driver/mysql" ) func main() { // Set up a local mysql docker container with: // docker run -it -p 3306:3306 --net "bridge" -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql - db, err := sql.Open("nrmysql", "root@/information_schema") + db, err := sql.Open("mysql", "root@/information_schema") if err != nil { panic(err) } - app, err := newrelic.NewApplication( - newrelic.ConfigAppName("MySQL App"), - newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), - newrelic.ConfigDebugLogger(os.Stdout), - ) - if err != nil { - panic(err) - } - app.WaitForConnection(5 * time.Second) - row := db.QueryRow("SELECT count(*) from tables") var count int row.Scan(&count) - app.Shutdown(5 * time.Second) fmt.Println("number of tables in information_schema", count) } diff --git a/parser/mysql.go b/integrations/nrmysql/mysql.go similarity index 85% rename from parser/mysql.go rename to integrations/nrmysql/mysql.go index 23650ae8..04498aec 100644 --- a/parser/mysql.go +++ b/integrations/nrmysql/mysql.go @@ -1,14 +1,15 @@ -package parser +package nrmysql import ( "github.com/dave/dst" "github.com/dave/dst/dstutil" "github.com/newrelic/go-easy-instrumentation/internal/codegen" + "github.com/newrelic/go-easy-instrumentation/parser" "github.com/newrelic/go-easy-instrumentation/parser/tracestate" ) const ( - sqlImportPath = "database/sql" + SqlImportPath = "database/sql" ) // detectSQLExecutionCall checks if a statement contains a SQL query operation using the given DB variable. @@ -17,7 +18,7 @@ const ( // Example: row := db.QueryRow("SELECT count(*) from tables") // // ^^ -func detectSQLExecutionCall(stmt dst.Stmt, dbName string) string { +func DetectSQLExecutionCall(stmt dst.Stmt, dbName string) string { assignStmt, ok := stmt.(*dst.AssignStmt) if !ok || len(assignStmt.Rhs) != 1 { return "" @@ -56,7 +57,7 @@ func detectSQLExecutionCall(stmt dst.Stmt, dbName string) string { // - QueryRow -> QueryRowContext // - Query -> QueryContext // - Exec -> ExecContext -func replaceSQLMethodWithContext(stmt dst.Stmt, ctxName string) { +func ReplaceSQLMethodWithContext(stmt dst.Stmt, ctxName string) { assignStmt, ok := stmt.(*dst.AssignStmt) if !ok || len(assignStmt.Rhs) != 1 { return @@ -94,7 +95,7 @@ func replaceSQLMethodWithContext(stmt dst.Stmt, ctxName string) { // Example: db, err := sql.Open("nrmysql", "root@/information_schema") // // ^^ -func detectSQLOpenCall(stmt dst.Stmt) string { +func DetectSQLOpenCall(stmt dst.Stmt) string { assignStmt, ok := stmt.(*dst.AssignStmt) if !ok || len(assignStmt.Rhs) != 1 || len(assignStmt.Lhs) == 0 { return "" @@ -106,7 +107,7 @@ func detectSQLOpenCall(stmt dst.Stmt) string { } ident, ok := call.Fun.(*dst.Ident) - if !ok || ident.Name != "Open" || ident.Path != sqlImportPath { + if !ok || ident.Name != "Open" || ident.Path != SqlImportPath { return "" } @@ -121,7 +122,7 @@ func detectSQLOpenCall(stmt dst.Stmt) string { // findLastUsageOfExecutionResult scans statements starting from startIndex to find the last // usage of the given variable name. Returns the index of the last usage, or startIndex // if the variable is never used after that point. -func findLastUsageOfExecutionResult(stmts []dst.Stmt, varName string, startIndex int) int { +func FindLastUsageOfExecutionResult(stmts []dst.Stmt, varName string, startIndex int) int { lastUsageIndex := startIndex for i := startIndex + 1; i < len(stmts); i++ { @@ -150,7 +151,7 @@ func findLastUsageOfExecutionResult(stmts []dst.Stmt, varName string, startIndex // 4. Converting SQL methods to their context-aware versions // 5. Inserting the transaction context before the SQL call // 6. Ending the transaction after the result is consumed -func InstrumentSQLHandler(manager *InstrumentationManager, c *dstutil.Cursor) { +func InstrumentSQLHandler(manager *parser.InstrumentationManager, c *dstutil.Cursor) { funcDecl, ok := c.Node().(*dst.FuncDecl) if !ok || funcDecl.Name.Name != "main" { return @@ -167,14 +168,14 @@ func InstrumentSQLHandler(manager *InstrumentationManager, c *dstutil.Cursor) { // Scan function body to find SQL operations for i, stmt := range funcDecl.Body.List { // Detect sql.Open() to find the DB variable - if dbName := detectSQLOpenCall(stmt); dbName != "" { + if dbName := DetectSQLOpenCall(stmt); dbName != "" { sqlDB = dbName continue } // Once we have a DB variable, look for SQL execution calls if sqlDB != "" { - if methodName := detectSQLExecutionCall(stmt, sqlDB); methodName != "" { + if methodName := DetectSQLExecutionCall(stmt, sqlDB); methodName != "" { sqlExecutionIndex = i sqlMethodName = methodName @@ -197,19 +198,19 @@ func InstrumentSQLHandler(manager *InstrumentationManager, c *dstutil.Cursor) { // Find where the SQL result variable is last used to determine where to end the transaction lastUsageIndex := sqlExecutionIndex if sqlResultVar != "" { - lastUsageIndex = findLastUsageOfExecutionResult(funcDecl.Body.List, sqlResultVar, sqlExecutionIndex) + lastUsageIndex = FindLastUsageOfExecutionResult(funcDecl.Body.List, sqlResultVar, sqlExecutionIndex) } // Generate instrumentation code txnName := codegen.DefaultTransactionVariable ctxName := "ctx" - txnStart := codegen.CreateSQLTransaction(manager.agentVariableName, txnName, sqlMethodName) - ctxAssignment := codegen.CreateContextWithTransaction(ctxName, txnName) - txnEnd := codegen.CreateTransactionEnd(txnName) + txnStart := CreateSQLTransaction(manager.AgentVariableName(), txnName, sqlMethodName) + ctxAssignment := CreateContextWithTransaction(ctxName, txnName) + txnEnd := CreateTransactionEnd(txnName) // Transform the SQL method to use context (e.g., QueryRow -> QueryRowContext) - replaceSQLMethodWithContext(funcDecl.Body.List[sqlExecutionIndex], ctxName) + ReplaceSQLMethodWithContext(funcDecl.Body.List[sqlExecutionIndex], ctxName) // Insert transaction end after the last usage of the result variable funcDecl.Body.List = append( @@ -224,8 +225,8 @@ func InstrumentSQLHandler(manager *InstrumentationManager, c *dstutil.Cursor) { ) // Add required imports - manager.addImport(codegen.NewRelicAgentImportPath) - manager.addImport("context") + manager.AddImport(codegen.NewRelicAgentImportPath) + manager.AddImport("context") // Wrap the main function with New Relic agent initialization tc := tracestate.FunctionBody(txnName) diff --git a/parser/mysql_test.go b/integrations/nrmysql/parsing_test.go similarity index 92% rename from parser/mysql_test.go rename to integrations/nrmysql/parsing_test.go index bc2194cd..66e8dda0 100644 --- a/parser/mysql_test.go +++ b/integrations/nrmysql/parsing_test.go @@ -1,6 +1,8 @@ -package parser +package nrmysql_test import ( + "github.com/newrelic/go-easy-instrumentation/integrations/nrmysql" + "github.com/newrelic/go-easy-instrumentation/parser" "go/token" "testing" @@ -19,6 +21,8 @@ func TestInstrumentSQLHandler(t *testing.T) { code: `package main import ( + "github.com/newrelic/go-easy-instrumentation/integrations/nrmysql" + "github.com/newrelic/go-easy-instrumentation/parser" "database/sql" _ "github.com/go-sql-driver/mysql" ) @@ -126,8 +130,8 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentSQLHandler) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nrmysql.InstrumentSQLHandler) assert.Equal(t, tt.expect, got) }) } @@ -151,7 +155,7 @@ func TestDetectSQLOpenCall(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "Open", - Path: sqlImportPath, + Path: nrmysql.SqlImportPath, }, Args: []dst.Expr{ &dst.BasicLit{Value: `"nrmysql"`}, @@ -192,7 +196,7 @@ func TestDetectSQLOpenCall(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "Connect", - Path: sqlImportPath, + Path: nrmysql.SqlImportPath, }, }, }, @@ -212,8 +216,8 @@ func TestDetectSQLOpenCall(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := detectSQLOpenCall(tt.stmt) + defer parser.PanicRecovery(t) + got := nrmysql.DetectSQLOpenCall(tt.stmt) assert.Equal(t, tt.want, got) }) } @@ -330,8 +334,8 @@ func TestDetectSQLExecutionCall(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := detectSQLExecutionCall(tt.stmt, tt.dbName) + defer parser.PanicRecovery(t) + got := nrmysql.DetectSQLExecutionCall(tt.stmt, tt.dbName) assert.Equal(t, tt.want, got) }) } @@ -414,8 +418,8 @@ func TestReplaceSQLMethodWithContext(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - replaceSQLMethodWithContext(tt.stmt, tt.ctxName) + defer parser.PanicRecovery(t) + nrmysql.ReplaceSQLMethodWithContext(tt.stmt, tt.ctxName) assignStmt := tt.stmt.(*dst.AssignStmt) callExpr := assignStmt.Rhs[0].(*dst.CallExpr) @@ -551,8 +555,8 @@ func TestFindLastUsageOfVariable(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := findLastUsageOfExecutionResult(tt.stmts, tt.varName, tt.startIndex) + defer parser.PanicRecovery(t) + got := nrmysql.FindLastUsageOfExecutionResult(tt.stmts, tt.varName, tt.startIndex) assert.Equal(t, tt.want, got) }) } diff --git a/internal/codegen/http.go b/integrations/nrnethttp/codegen.go similarity index 87% rename from internal/codegen/http.go rename to integrations/nrnethttp/codegen.go index db3fbdaa..c344b3f4 100644 --- a/internal/codegen/http.go +++ b/integrations/nrnethttp/codegen.go @@ -1,28 +1,16 @@ -package codegen +package nrnethttp import ( "go/token" "github.com/dave/dst" + "github.com/newrelic/go-easy-instrumentation/internal/codegen" ) const ( HttpImportPath = "net/http" ) -func HttpRequestContext(reqArgName string) dst.Expr { - return &dst.CallExpr{ - Fun: &dst.SelectorExpr{ - X: &dst.Ident{ - Name: reqArgName, - }, - Sel: &dst.Ident{ - Name: "Context", - }, - }, - } -} - // WrapHttpHandleFunc does an in place edit of a call expression to http.HandleFunc // replacing it with a call to newrelic.WrapHandleFunc // @@ -34,7 +22,7 @@ func WrapHttpHandleFunc(agentVariable dst.Expr, handle *dst.CallExpr) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "WrapHandleFunc", - Path: NewRelicAgentImportPath, + Path: codegen.NewRelicAgentImportPath, }, Args: []dst.Expr{ agentVariable, @@ -56,7 +44,7 @@ func WrapHttpHandle(agentVariable dst.Expr, handle *dst.CallExpr) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "WrapHandle", - Path: NewRelicAgentImportPath, + Path: codegen.NewRelicAgentImportPath, }, Args: []dst.Expr{ agentVariable, @@ -80,7 +68,7 @@ func RoundTripper(clientVariable dst.Expr, spacingAfter dst.SpaceType) *dst.Assi &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewRoundTripper", - Path: NewRelicAgentImportPath, + Path: codegen.NewRelicAgentImportPath, }, Args: []dst.Expr{ &dst.SelectorExpr{ @@ -121,7 +109,7 @@ func WrapRequestContext(request dst.Expr, txnVariable dst.Expr, nodeDecs *dst.No &dst.CallExpr{ Fun: &dst.Ident{ Name: "RequestWithTransactionContext", - Path: NewRelicAgentImportPath, + Path: codegen.NewRelicAgentImportPath, }, Args: []dst.Expr{ dst.Clone(request).(dst.Expr), diff --git a/internal/codegen/http_test.go b/integrations/nrnethttp/codegen_test.go similarity index 76% rename from internal/codegen/http_test.go rename to integrations/nrnethttp/codegen_test.go index cf39f43e..d8984a64 100644 --- a/internal/codegen/http_test.go +++ b/integrations/nrnethttp/codegen_test.go @@ -1,14 +1,17 @@ -package codegen +package nrnethttp_test import ( "go/token" "reflect" "testing" + "github.com/newrelic/go-easy-instrumentation/integrations/nrnethttp" + "github.com/newrelic/go-easy-instrumentation/internal/codegen" + "github.com/dave/dst" ) -func Test_injectRoundTripper(t *testing.T) { +func TestRoundTripper(t *testing.T) { type args struct { clientVariable dst.Expr spacingAfter dst.SpaceType @@ -36,7 +39,7 @@ func Test_injectRoundTripper(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "NewRoundTripper", - Path: NewRelicAgentImportPath, + Path: codegen.NewRelicAgentImportPath, }, Args: []dst.Expr{ &dst.SelectorExpr{ @@ -56,14 +59,14 @@ func Test_injectRoundTripper(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := RoundTripper(tt.args.clientVariable, tt.args.spacingAfter); !reflect.DeepEqual(got, tt.want) { - t.Errorf("injectRoundTripper() = %v, want %v", got, tt.want) + if got := nrnethttp.RoundTripper(tt.args.clientVariable, tt.args.spacingAfter); !reflect.DeepEqual(got, tt.want) { + t.Errorf("injectnrnethttp.RoundTripper() = %v, want %v", got, tt.want) } }) } } -func Test_addTxnToRequestContext(t *testing.T) { +func TestAddTxnToRequestContext(t *testing.T) { type args struct { request dst.Expr txnVar dst.Expr @@ -79,7 +82,7 @@ func Test_addTxnToRequestContext(t *testing.T) { args: args{ request: &dst.Ident{ Name: "r", - Path: HttpImportPath, + Path: nrnethttp.HttpImportPath, }, txnVar: dst.NewIdent("txn"), nodeDecs: &dst.NodeDecs{ @@ -91,18 +94,18 @@ func Test_addTxnToRequestContext(t *testing.T) { Tok: token.ASSIGN, Lhs: []dst.Expr{dst.Clone(&dst.Ident{ Name: "r", - Path: HttpImportPath, + Path: nrnethttp.HttpImportPath, }).(dst.Expr)}, Rhs: []dst.Expr{ &dst.CallExpr{ Fun: &dst.Ident{ Name: "RequestWithTransactionContext", - Path: NewRelicAgentImportPath, + Path: codegen.NewRelicAgentImportPath, }, Args: []dst.Expr{ dst.Clone(&dst.Ident{ Name: "r", - Path: HttpImportPath, + Path: nrnethttp.HttpImportPath, }).(dst.Expr), dst.NewIdent("txn"), }, @@ -119,7 +122,7 @@ func Test_addTxnToRequestContext(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := WrapRequestContext(tt.args.request, tt.args.txnVar, tt.args.nodeDecs); !reflect.DeepEqual(got, tt.want) { + if got := nrnethttp.WrapRequestContext(tt.args.request, tt.args.txnVar, tt.args.nodeDecs); !reflect.DeepEqual(got, tt.want) { t.Errorf("addTxnToRequestContext() = %v, want %v", got, tt.want) } if len(tt.args.nodeDecs.Start) != 0 { diff --git a/end-to-end-tests/http-app/expect.ref b/integrations/nrnethttp/example/http-app/expect.ref similarity index 100% rename from end-to-end-tests/http-app/expect.ref rename to integrations/nrnethttp/example/http-app/expect.ref diff --git a/integrations/nrnethttp/example/http-app/go.mod b/integrations/nrnethttp/example/http-app/go.mod new file mode 100644 index 00000000..056618f9 --- /dev/null +++ b/integrations/nrnethttp/example/http-app/go.mod @@ -0,0 +1,13 @@ +module http-app + +go 1.24 + +toolchain go1.24.11 + +require github.com/stretchr/testify v1.10.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/integrations/nrnethttp/example/http-app/go.sum b/integrations/nrnethttp/example/http-app/go.sum new file mode 100644 index 00000000..713a0b4f --- /dev/null +++ b/integrations/nrnethttp/example/http-app/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/end-to-end-tests/http-app/main.go b/integrations/nrnethttp/example/http-app/main.go similarity index 100% rename from end-to-end-tests/http-app/main.go rename to integrations/nrnethttp/example/http-app/main.go diff --git a/end-to-end-tests/http-app/main_test.go b/integrations/nrnethttp/example/http-app/main_test.go similarity index 100% rename from end-to-end-tests/http-app/main_test.go rename to integrations/nrnethttp/example/http-app/main_test.go diff --git a/end-to-end-tests/http-app/pkg/service.go b/integrations/nrnethttp/example/http-app/pkg/service.go similarity index 100% rename from end-to-end-tests/http-app/pkg/service.go rename to integrations/nrnethttp/example/http-app/pkg/service.go diff --git a/end-to-end-tests/http-mux-app/expect.ref b/integrations/nrnethttp/example/http-mux-app/expect.ref similarity index 100% rename from end-to-end-tests/http-mux-app/expect.ref rename to integrations/nrnethttp/example/http-mux-app/expect.ref diff --git a/end-to-end-tests/http-mux-app/go.mod b/integrations/nrnethttp/example/http-mux-app/go.mod similarity index 100% rename from end-to-end-tests/http-mux-app/go.mod rename to integrations/nrnethttp/example/http-mux-app/go.mod diff --git a/end-to-end-tests/http-mux-app/go.sum b/integrations/nrnethttp/example/http-mux-app/go.sum similarity index 100% rename from end-to-end-tests/http-mux-app/go.sum rename to integrations/nrnethttp/example/http-mux-app/go.sum diff --git a/end-to-end-tests/http-mux-app/server.go b/integrations/nrnethttp/example/http-mux-app/server.go similarity index 100% rename from end-to-end-tests/http-mux-app/server.go rename to integrations/nrnethttp/example/http-mux-app/server.go diff --git a/parser/netHTTP.go b/integrations/nrnethttp/http.go similarity index 77% rename from parser/netHTTP.go rename to integrations/nrnethttp/http.go index c9529fad..4eca0c72 100644 --- a/parser/netHTTP.go +++ b/integrations/nrnethttp/http.go @@ -1,4 +1,4 @@ -package parser +package nrnethttp import ( "fmt" @@ -11,6 +11,7 @@ import ( "github.com/newrelic/go-easy-instrumentation/internal/codegen" "github.com/newrelic/go-easy-instrumentation/internal/comment" "github.com/newrelic/go-easy-instrumentation/internal/util" + "github.com/newrelic/go-easy-instrumentation/parser" "github.com/newrelic/go-easy-instrumentation/parser/tracestate" ) @@ -33,8 +34,8 @@ const ( // RouterHasMiddleware detects already existing net/http routers and marks them within the scope of the given transaction. // It returns true if the function name matches within a wrapped HandleFunc, false otherwise. // TO:DO -- Can this be extended to ALL routing libraries? -func HandlerIsInstrumented(manager *InstrumentationManager, fn *dst.FuncDecl) bool { - txns := manager.transactionCache.Transactions +func HandlerIsInstrumented(manager *parser.InstrumentationManager, fn *dst.FuncDecl) bool { + txns := manager.TransactionCache().Transactions for ident := range txns { if ident == fn.Name { return true @@ -45,7 +46,7 @@ func HandlerIsInstrumented(manager *InstrumentationManager, fn *dst.FuncDecl) bo // GetNetHttpClientVariableName looks for an http client in the call expression n. If it finds one, the name // of the variable containing the client will be returned as a string. -func getNetHttpClientVariableName(n *dst.CallExpr, pkg *decorator.Package) string { +func GetNetHttpClientVariableName(n *dst.CallExpr, pkg *decorator.Package) string { if n == nil { return "" } @@ -55,12 +56,12 @@ func getNetHttpClientVariableName(n *dst.CallExpr, pkg *decorator.Package) strin switch v := Sel.X.(type) { case *dst.SelectorExpr: path := util.PackagePath(v.Sel, pkg) - if path == codegen.HttpImportPath { + if path == HttpImportPath { return v.Sel.Name } case *dst.Ident: path := util.PackagePath(v, pkg) - if path == codegen.HttpImportPath { + if path == HttpImportPath { return v.Name } } @@ -71,8 +72,8 @@ func getNetHttpClientVariableName(n *dst.CallExpr, pkg *decorator.Package) strin // extract the request arg name from a declared route handler // func handler(w http.ResponseWriter, r *http.Request) // ____________________________________^ -func getHTTPRequestArgNameDecl(fn *dst.FuncDecl) (bool, string) { - if !isHTTPHandlerDecl(fn) { +func GetHTTPRequestArgNameDecl(fn *dst.FuncDecl) (bool, string) { + if !IsHTTPHandlerDecl(fn) { return false, "" } @@ -82,8 +83,8 @@ func getHTTPRequestArgNameDecl(fn *dst.FuncDecl) (bool, string) { // extract the request arg name from a literal route handler // func(w http.ResponseWriter, r *http.Request) // ____________________________^ -func getHTTPRequestArgNameLit(fn *dst.FuncLit) (bool, string) { - if !isHTTPHandlerLit(fn) { +func GetHTTPRequestArgNameLit(fn *dst.FuncLit) (bool, string) { + if !IsHTTPHandlerLit(fn) { return false, "" } @@ -93,7 +94,7 @@ func getHTTPRequestArgNameLit(fn *dst.FuncLit) (bool, string) { // wrapper for HTTP request arg extraction. // take in `any` and filter non FuncDecl and non FuncLit, then // dispatch to appropriate function. -func getHTTPRequestArgName(fn any) (bool, string) { +func GetHTTPRequestArgName(fn any) (bool, string) { if fn == nil { return false, "" } @@ -103,9 +104,9 @@ func getHTTPRequestArgName(fn any) (bool, string) { switch f := fn.(type) { case *dst.FuncDecl: - ok, name = getHTTPRequestArgNameDecl(f) + ok, name = GetHTTPRequestArgNameDecl(f) case *dst.FuncLit: - ok, name = getHTTPRequestArgNameLit(f) + ok, name = GetHTTPRequestArgNameLit(f) default: return false, "" } @@ -113,7 +114,7 @@ func getHTTPRequestArgName(fn any) (bool, string) { } // GetNetHttpMethod gets an http method if one is invoked in the call expression n, and returns the name of it as a string -func getNetHttpMethod(n *dst.CallExpr, pkg *decorator.Package) string { +func GetNetHttpMethod(n *dst.CallExpr, pkg *decorator.Package) string { if n == nil { return "" } @@ -121,12 +122,12 @@ func getNetHttpMethod(n *dst.CallExpr, pkg *decorator.Package) string { switch v := n.Fun.(type) { case *dst.SelectorExpr: path := util.PackagePath(v.Sel, pkg) - if path == codegen.HttpImportPath { + if path == HttpImportPath { return v.Sel.Name } case *dst.Ident: path := util.PackagePath(v, pkg) - if path == codegen.HttpImportPath { + if path == HttpImportPath { return v.Name } } @@ -135,9 +136,9 @@ func getNetHttpMethod(n *dst.CallExpr, pkg *decorator.Package) string { } // txnFromCtx injects a line of code that extracts a transaction from the context into the body of a function -func defineTxnFromCtx(fn *dst.FuncDecl, txnVariable string) { +func DefineTxnFromCtx(fn *dst.FuncDecl, txnVariable string) { stmts := make([]dst.Stmt, len(fn.Body.List)+1) - ok, reqArgName := getHTTPRequestArgName(fn) + ok, reqArgName := GetHTTPRequestArgName(fn) if !ok { // TODO: consider injecting a comment or creating a log message describing the failure here. return @@ -149,12 +150,12 @@ func defineTxnFromCtx(fn *dst.FuncDecl, txnVariable string) { fn.Body.List = stmts } -func isHTTPHandler(fn any) bool { +func IsHTTPHandler(fn any) bool { switch f := fn.(type) { case *dst.FuncLit: - return isHTTPHandlerLit(f) + return IsHTTPHandlerLit(f) case *dst.FuncDecl: - return isHTTPHandlerDecl(f) + return IsHTTPHandlerDecl(f) default: return false } @@ -175,7 +176,7 @@ func isHTTPResponseWriter(respW *dst.Field) bool { return false } - if identRespW.Path != codegen.HttpImportPath || identRespW.Name != "ResponseWriter" { + if identRespW.Path != HttpImportPath || identRespW.Name != "ResponseWriter" { return false } return true @@ -201,7 +202,7 @@ func isHTTPRequest(req *dst.Field) bool { return false } - if identReq.Path != codegen.HttpImportPath || identReq.Name != "Request" { + if identReq.Path != HttpImportPath || identReq.Name != "Request" { return false } return true @@ -211,7 +212,7 @@ func isHTTPRequest(req *dst.Field) bool { // handler argument signatures // func myHandler(w http.ResponseWriter, r *http.Request) // _______________^______________________^ -func isHTTPHandlerDecl(fn *dst.FuncDecl) bool { +func IsHTTPHandlerDecl(fn *dst.FuncDecl) bool { if fn == nil || fn.Type == nil || fn.Type.Params == nil || fn.Type.Params.List == nil { return false } @@ -238,7 +239,7 @@ func isHTTPHandlerDecl(fn *dst.FuncDecl) bool { // handler argument signatures // func(w http.ResponseWriter, r *http.Request) // _____^______________________^ -func isHTTPHandlerLit(fn *dst.FuncLit) bool { +func IsHTTPHandlerLit(fn *dst.FuncLit) bool { if fn == nil || fn.Type == nil || fn.Type.Params == nil || fn.Type.Params.List == nil { return false } @@ -333,14 +334,14 @@ func clientTransportAlreadyInstrumented(c *dstutil.Cursor, clientVarName string) } // more unit test friendly helper function -func isNetHttpClientDefinition(stmt *dst.AssignStmt) bool { +func IsNetHttpClientDefinition(stmt *dst.AssignStmt) bool { if len(stmt.Rhs) == 1 && len(stmt.Lhs) == 1 && (stmt.Tok == token.DEFINE) { unary, ok := stmt.Rhs[0].(*dst.UnaryExpr) if ok && unary.Op == token.AND { lit, ok := unary.X.(*dst.CompositeLit) if ok { ident, ok := lit.Type.(*dst.Ident) - if ok && ident.Name == "Client" && ident.Path == codegen.HttpImportPath { + if ok && ident.Name == "Client" && ident.Path == HttpImportPath { return true } } @@ -355,16 +356,16 @@ func isNetHttpClientDefinition(stmt *dst.AssignStmt) bool { // Recognize if a function is a handler func based on its contents, and inject instrumentation. // This function discovers entrypoints to tracing for a given transaction and should trace all the way // down the call chain of the function it is invoked on. -func InstrumentHandleFunction(manager *InstrumentationManager, c *dstutil.Cursor) { +func InstrumentHandleFunction(manager *parser.InstrumentationManager, c *dstutil.Cursor) { n := c.Node() fn, isFn := n.(*dst.FuncDecl) // TODO: 'isFn' should be renamed to 'ok' to match the paradigm in the rest of the codebase. - if isFn && isHTTPHandler(fn) && !HandlerIsInstrumented(manager, fn) { + if isFn && IsHTTPHandler(fn) && !HandlerIsInstrumented(manager, fn) { - comment.Debug(manager.getDecoratorPackage(), fn, fmt.Sprintf("Instrumenting HTTP handler: %s", fn.Name.Name)) + comment.Debug(manager.GetDecoratorPackage(), fn, fmt.Sprintf("Instrumenting HTTP handler: %s", fn.Name.Name)) txnName := codegen.DefaultTransactionVariable - newFn, ok := TraceFunction(manager, fn, tracestate.FunctionBody(txnName)) + newFn, ok := parser.TraceFunction(manager, fn, tracestate.FunctionBody(txnName)) if ok { - defineTxnFromCtx(newFn.(*dst.FuncDecl), txnName) // pass the transaction + DefineTxnFromCtx(newFn.(*dst.FuncDecl), txnName) // pass the transaction } } @@ -373,17 +374,17 @@ func InstrumentHandleFunction(manager *InstrumentationManager, c *dstutil.Cursor // InstrumentHttpClient automatically injects a newrelic roundtripper into any newly created http client // looks for the following pattern: client := &http.Client{} // Additionally, it also checks if the transport is already instrumented to avoid duplicate injection -func InstrumentHttpClient(manager *InstrumentationManager, c *dstutil.Cursor) { +func InstrumentHttpClient(manager *parser.InstrumentationManager, c *dstutil.Cursor) { n := c.Node() stmt, ok := n.(*dst.AssignStmt) - if ok && isNetHttpClientDefinition(stmt) && c.Index() >= 0 && n.Decorations() != nil { - c.InsertAfter(codegen.RoundTripper(stmt.Lhs[0], n.Decorations().After)) // add roundtripper to transports + if ok && IsNetHttpClientDefinition(stmt) && c.Index() >= 0 && n.Decorations() != nil { + c.InsertAfter(RoundTripper(stmt.Lhs[0], n.Decorations().After)) // add roundtripper to transports stmt.Decs.After = dst.None - manager.addImport(codegen.NewRelicAgentImportPath) + manager.AddImport(codegen.NewRelicAgentImportPath) } } -func cannotTraceOutboundHttp(method string, decs *dst.NodeDecs) []string { +func CannotTraceOutboundHttp(method string, decs *dst.NodeDecs) []string { comment := []string{ fmt.Sprintf("// the \"http.%s()\" net/http method can not be instrumented and its outbound traffic can not be traced", method), "// please see these examples of code patterns for external http calls that can be instrumented:", @@ -399,7 +400,7 @@ func cannotTraceOutboundHttp(method string, decs *dst.NodeDecs) []string { // isNetHttpMethodCannotInstrument is a function that discovers methods of net/http that can not be instrumented by new relic // and returns the name of the method and whether it can be instrumented or not. -func isNetHttpMethodCannotInstrument(node dst.Node) (string, bool) { +func IsNetHttpMethodCannotInstrument(node dst.Node) (string, bool) { var cannotInstrument bool var returnFuncName string @@ -414,7 +415,7 @@ func isNetHttpMethodCannotInstrument(node dst.Node) (string, bool) { c, ok := n.(*dst.CallExpr) if ok { ident, ok := c.Fun.(*dst.Ident) - if ok && ident.Path == codegen.HttpImportPath { + if ok && ident.Path == HttpImportPath { switch ident.Name { case httpGet, httpPost, httpPostForm, httpHead: returnFuncName = ident.Name @@ -431,12 +432,12 @@ func isNetHttpMethodCannotInstrument(node dst.Node) (string, bool) { // CannotInstrumentHttpMethod is a function that discovers methods of net/http. If that function can not be penetrated by // instrumentation, it leaves a comment header warning the customer. This function needs no tracing context to work. -func CannotInstrumentHttpMethod(manager *InstrumentationManager, c *dstutil.Cursor) { +func CannotInstrumentHttpMethod(manager *parser.InstrumentationManager, c *dstutil.Cursor) { n := c.Node() - funcName, ok := isNetHttpMethodCannotInstrument(n) + funcName, ok := IsNetHttpMethodCannotInstrument(n) if ok { if decl := n.Decorations(); decl != nil { - decl.Start.Prepend(cannotTraceOutboundHttp(funcName, n.Decorations())...) + decl.Start.Prepend(CannotTraceOutboundHttp(funcName, n.Decorations())...) } } } @@ -445,9 +446,9 @@ func CannotInstrumentHttpMethod(manager *InstrumentationManager, c *dstutil.Curs ////////////////////////////////////////////// // getHttpResponseVariable returns the expression that contains an object of `*net/http.Response` type -func getHttpResponseVariable(manager *InstrumentationManager, stmt dst.Stmt) dst.Expr { +func GetHttpResponseVariable(manager *parser.InstrumentationManager, stmt dst.Stmt) dst.Expr { var expression dst.Expr - pkg := manager.getDecoratorPackage() + pkg := manager.GetDecoratorPackage() dst.Inspect(stmt, func(n dst.Node) bool { switch v := n.(type) { case *dst.BlockStmt: @@ -473,18 +474,18 @@ func getHttpResponseVariable(manager *InstrumentationManager, stmt dst.Stmt) dst // ExternalHttpCall finds and instruments external net/http calls to the method http.Do. // It returns true if a modification was made -func ExternalHttpCall(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { +func ExternalHttpCall(manager *parser.InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { if c.Index() < 0 { return false } - pkg := manager.getDecoratorPackage() + pkg := manager.GetDecoratorPackage() var call *dst.CallExpr dst.Inspect(stmt, func(n dst.Node) bool { switch v := n.(type) { case *dst.BlockStmt: return false case *dst.CallExpr: - if getNetHttpMethod(v, pkg) == httpDo { + if GetNetHttpMethod(v, pkg) == httpDo { call = v return false } @@ -492,24 +493,24 @@ func ExternalHttpCall(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil return true }) if call != nil && c.Index() >= 0 { - clientVar := getNetHttpClientVariableName(call, pkg) + clientVar := GetNetHttpClientVariableName(call, pkg) requestObject := call.Args[0] if clientVar == httpDefaultClientVariable { // create external segment to wrap calls made with default client - comment.Debug(manager.getDecoratorPackage(), stmt, "Wrapping default HTTP client call with external segment") + comment.Debug(manager.GetDecoratorPackage(), stmt, "Wrapping default HTTP client call with external segment") segmentName := "externalSegment" c.InsertBefore(codegen.StartExternalSegment(requestObject, tracing.TransactionVariable(), segmentName, stmt.Decorations())) c.InsertAfter(codegen.EndExternalSegment(segmentName, stmt.Decorations())) - responseVar := getHttpResponseVariable(manager, stmt) - manager.addImport(codegen.NewRelicAgentImportPath) + responseVar := GetHttpResponseVariable(manager, stmt) + manager.AddImport(codegen.NewRelicAgentImportPath) if responseVar != nil { c.InsertAfter(codegen.CaptureHttpResponse(segmentName, responseVar)) } return true } else { - comment.Debug(manager.getDecoratorPackage(), stmt, "Injecting transaction context into HTTP request") - c.InsertBefore(codegen.WrapRequestContext(requestObject, tracing.TransactionVariable(), stmt.Decorations())) - manager.addImport(codegen.NewRelicAgentImportPath) + comment.Debug(manager.GetDecoratorPackage(), stmt, "Injecting transaction context into HTTP request") + c.InsertBefore(WrapRequestContext(requestObject, tracing.TransactionVariable(), stmt.Decorations())) + manager.AddImport(codegen.NewRelicAgentImportPath) return true } } @@ -518,35 +519,35 @@ func ExternalHttpCall(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil // WrapHandleFunction is a function that wraps net/http.HandeFunc() declarations inside of functions // that are being traced by a transaction. -func WrapNestedHandleFunction(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { +func WrapNestedHandleFunction(manager *parser.InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { wasModified := false - pkg := manager.getDecoratorPackage() + pkg := manager.GetDecoratorPackage() dst.Inspect(stmt, func(n dst.Node) bool { switch v := n.(type) { case *dst.BlockStmt: return false case *dst.CallExpr: callExpr := v - funcName := getNetHttpMethod(callExpr, pkg) + funcName := GetNetHttpMethod(callExpr, pkg) switch funcName { case httpHandleFunc: if len(callExpr.Args) == 2 { // Instrument handle funcs - comment.Debug(manager.getDecoratorPackage(), stmt, "Wrapping http.HandleFunc with newrelic.WrapHandleFunc") - codegen.WrapHttpHandleFunc(tracing.AgentVariable(), callExpr) + comment.Debug(manager.GetDecoratorPackage(), stmt, "Wrapping http.HandleFunc with newrelic.WrapHandleFunc") + WrapHttpHandleFunc(tracing.AgentVariable(), callExpr) wasModified = true - manager.addImport(codegen.NewRelicAgentImportPath) + manager.AddImport(codegen.NewRelicAgentImportPath) return false } case httpMuxHandle: if len(callExpr.Args) == 2 { // Instrument handle funcs - comment.Debug(manager.getDecoratorPackage(), stmt, "Wrapping http.Handle with newrelic.WrapHandle") - codegen.WrapHttpHandle(tracing.AgentVariable(), callExpr) + comment.Debug(manager.GetDecoratorPackage(), stmt, "Wrapping http.Handle with newrelic.WrapHandle") + WrapHttpHandle(tracing.AgentVariable(), callExpr) wasModified = true - manager.addImport(codegen.NewRelicAgentImportPath) + manager.AddImport(codegen.NewRelicAgentImportPath) return false } } @@ -560,7 +561,7 @@ func WrapNestedHandleFunction(manager *InstrumentationManager, stmt dst.Stmt, c // Pre-Instrumentation Tracing Functions //////////////////////////// -func DetectWrappedRoutes(manager *InstrumentationManager, c *dstutil.Cursor) { +func DetectWrappedRoutes(manager *parser.InstrumentationManager, c *dstutil.Cursor) { mainFunctionNode := c.Node() if decl, ok := mainFunctionNode.(*dst.FuncDecl); ok { // Check if we're in the main function @@ -593,9 +594,9 @@ func DetectWrappedRoutes(manager *InstrumentationManager, c *dstutil.Cursor) { if !ok { continue } - fun := manager.transactionCache.Functions[funcName.Name] + fun := manager.TransactionCache().Functions[funcName.Name] if fun != nil { - manager.transactionCache.AddFuncDecl(fun) + manager.TransactionCache().AddFuncDecl(fun) } } diff --git a/parser/netHTTP_test.go b/integrations/nrnethttp/parsing_test.go similarity index 89% rename from parser/netHTTP_test.go rename to integrations/nrnethttp/parsing_test.go index 3344fc83..1be9e683 100644 --- a/parser/netHTTP_test.go +++ b/integrations/nrnethttp/parsing_test.go @@ -1,4 +1,4 @@ -package parser +package nrnethttp_test import ( "bytes" @@ -6,6 +6,10 @@ import ( "reflect" "testing" + "github.com/newrelic/go-easy-instrumentation/integrations/nragent" + "github.com/newrelic/go-easy-instrumentation/integrations/nrnethttp" + "github.com/newrelic/go-easy-instrumentation/parser" + "github.com/dave/dst" "github.com/dave/dst/decorator" "github.com/dave/dst/decorator/resolver/guess" @@ -13,7 +17,7 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_isNetHttpClient(t *testing.T) { +func TestIsNetHttpClient(t *testing.T) { tests := []struct { name string code string @@ -71,7 +75,7 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - pkgs := unitTest(t, tt.code) + pkgs := parser.UnitTest(t, tt.code) decl, ok := pkgs[0].Syntax[0].Decls[1].(*dst.FuncDecl) if !ok { t.Fatal("code must contain only one function declaration") @@ -82,14 +86,14 @@ func main() { t.Fatal("lineNum must point to an assignment statement") } - if got := isNetHttpClientDefinition(stmt); got != tt.want { + if got := nrnethttp.IsNetHttpClientDefinition(stmt); got != tt.want { t.Errorf("isNetHttpClient() = %v, want %v", got, tt.want) } }) } } -func Test_isNetHttpMethodCannotInstrument(t *testing.T) { +func TestIsNetHttpMethodCannotInstrument(t *testing.T) { tests := []struct { name string code string @@ -195,13 +199,13 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - pkgs := unitTest(t, tt.code) + pkgs := parser.UnitTest(t, tt.code) decl, ok := pkgs[0].Syntax[0].Decls[1].(*dst.FuncDecl) if !ok { t.Fatal("code must contain only one function declaration") } - gotFuncName, gotBool := isNetHttpMethodCannotInstrument(decl.Body.List[tt.lineNum]) + gotFuncName, gotBool := nrnethttp.IsNetHttpMethodCannotInstrument(decl.Body.List[tt.lineNum]) if gotBool != tt.wantBool { t.Errorf("isNetHttpMethodCannotInstrument() = %v, want %v", gotBool, tt.wantBool) } @@ -212,7 +216,7 @@ func main() { } } -func Test_isHttpHandler(t *testing.T) { +func TestIsHttpHandler(t *testing.T) { tests := []struct { name string code string @@ -252,14 +256,14 @@ func index(w http.ResponseWriter, r *http.Request, x string) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - pkgs := unitTest(t, tt.code) + pkgs := parser.UnitTest(t, tt.code) decl, ok := pkgs[0].Syntax[0].Decls[1].(*dst.FuncDecl) if !ok { t.Fatal("code must contain only one function declaration") } - gotBool := isHTTPHandler(decl) + gotBool := nrnethttp.IsHTTPHandler(decl) if gotBool != tt.wantBool { t.Errorf("isNetHttpMethodCannotInstrument() = %v, want %v", gotBool, tt.wantBool) } @@ -267,7 +271,7 @@ func index(w http.ResponseWriter, r *http.Request, x string) { } } -func Test_getNetHttpMethod(t *testing.T) { +func TestGetNetHttpMethod(t *testing.T) { tests := []struct { name string code string @@ -357,7 +361,7 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - pkgs := unitTest(t, tt.code) + pkgs := parser.UnitTest(t, tt.code) decl, ok := pkgs[0].Syntax[0].Decls[1].(*dst.FuncDecl) if !ok { @@ -374,7 +378,7 @@ func main() { t.Fatal("lineNum must point to an expression containing a call expression") } - gotFuncName := getNetHttpMethod(call, pkgs[0]) + gotFuncName := nrnethttp.GetNetHttpMethod(call, pkgs[0]) if gotFuncName != tt.wantFuncName { t.Errorf("isNetHttpMethodCannotInstrument() = %v, want %v", gotFuncName, tt.wantFuncName) @@ -383,7 +387,7 @@ func main() { } } -func Test_GetNetHttpClientVariableName(t *testing.T) { +func TestGetNetHttpClientVariableName(t *testing.T) { tests := []struct { name string code string @@ -451,7 +455,7 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - pkgs := unitTest(t, tt.code) + pkgs := parser.UnitTest(t, tt.code) decl, ok := pkgs[0].Syntax[0].Decls[1].(*dst.FuncDecl) if !ok { @@ -468,7 +472,7 @@ func main() { t.Fatal("lineNum must point to an expression containing a call expression") } - gotFuncName := getNetHttpClientVariableName(call, pkgs[0]) + gotFuncName := nrnethttp.GetNetHttpClientVariableName(call, pkgs[0]) if gotFuncName != tt.wantName { t.Errorf("isNetHttpMethodCannotInstrument() = %v, want %v", gotFuncName, tt.wantName) @@ -477,7 +481,7 @@ func main() { } } -func Test_cannotTraceOutboundHttp(t *testing.T) { +func TestCannotTraceOutboundHttp(t *testing.T) { type args struct { method string decs *dst.NodeDecs @@ -508,7 +512,7 @@ func Test_cannotTraceOutboundHttp(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := cannotTraceOutboundHttp(tt.args.method, tt.args.decs) + got := nrnethttp.CannotTraceOutboundHttp(tt.args.method, tt.args.decs) if tt.wantBuffer && got[len(got)-1] != "//" { t.Errorf("cannotTraceOutboundHttp() should add a comment ending in \"//\" but did NOT for method %s with decs %+v", tt.args.method, tt.args.decs) } @@ -519,7 +523,7 @@ func Test_cannotTraceOutboundHttp(t *testing.T) { } } -func Test_TxnFromCtx(t *testing.T) { +func TestTxnFromCtx(t *testing.T) { type args struct { fn *dst.FuncDecl txnVariable string @@ -543,7 +547,7 @@ func Test_TxnFromCtx(t *testing.T) { }, Type: &dst.Ident{ Name: "ResponseWriter", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, 1: { @@ -555,7 +559,7 @@ func Test_TxnFromCtx(t *testing.T) { Type: &dst.StarExpr{ X: &dst.Ident{ Name: "Request", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, }, @@ -584,7 +588,7 @@ func Test_TxnFromCtx(t *testing.T) { }, Type: &dst.Ident{ Name: "ResponseWriter", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, 1: { @@ -596,7 +600,7 @@ func Test_TxnFromCtx(t *testing.T) { Type: &dst.StarExpr{ X: &dst.Ident{ Name: "Request", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, }, @@ -616,7 +620,7 @@ func Test_TxnFromCtx(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { expectStmt := codegen.TxnFromContext(tt.args.txnVariable, codegen.HttpRequestContext("req")) - defineTxnFromCtx(tt.args.fn, tt.args.txnVariable) + nrnethttp.DefineTxnFromCtx(tt.args.fn, tt.args.txnVariable) if !reflect.DeepEqual(tt.args.fn.Body.List[0], expectStmt) { t.Errorf("expected the function body to contain the statement %v but got %v", expectStmt, tt.args.fn.Body.List[0]) } @@ -624,7 +628,7 @@ func Test_TxnFromCtx(t *testing.T) { } } -func Test_getHttpResponseVariable(t *testing.T) { +func TestGetHttpResponseVariable(t *testing.T) { tests := []struct { name string code string @@ -687,18 +691,18 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - id, err := pseudo_uuid() + id, err := parser.Pseudo_uuid() if err != nil { t.Fatal(err) } testDir := fmt.Sprintf("tmp_%s", id) - defer cleanTestApp(t, testDir) + defer parser.CleanTestApp(t, testDir) - manager := testInstrumentationManager(t, tt.code, testDir) - pkg := manager.getDecoratorPackage() + manager := parser.TestInstrumentationManager(t, tt.code, testDir) + pkg := manager.GetDecoratorPackage() stmt := pkg.Syntax[0].Decls[1].(*dst.FuncDecl).Body.List[tt.linenum] - gotExpr := getHttpResponseVariable(manager, stmt) + gotExpr := nrnethttp.GetHttpResponseVariable(manager, stmt) switch expect := tt.wantExpr.(type) { case *dst.Ident: got, ok := gotExpr.(*dst.Ident) @@ -851,8 +855,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatefulTracingFunction(t, tt.code, ExternalHttpCall, true) + defer parser.PanicRecovery(t) + got := parser.RunStatefulTracingFunction(t, tt.code, nrnethttp.ExternalHttpCall, true) assert.Equal(t, tt.expect, got) }) } @@ -918,8 +922,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatefulTracingFunction(t, tt.code, WrapNestedHandleFunction, true) + defer parser.PanicRecovery(t) + got := parser.RunStatefulTracingFunction(t, tt.code, nrnethttp.WrapNestedHandleFunction, true) assert.Equal(t, tt.expect, got) }) } @@ -979,8 +983,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, CannotInstrumentHttpMethod) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nrnethttp.CannotInstrumentHttpMethod) assert.Equal(t, tt.expect, got) }) } @@ -1051,8 +1055,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentHttpClient) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nrnethttp.InstrumentHttpClient) assert.Equal(t, tt.expect, got) }) } @@ -1139,8 +1143,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentHandleFunction) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nrnethttp.InstrumentHandleFunction) assert.Equal(t, tt.expect, got) }) } @@ -1232,8 +1236,8 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentMain, WrapNestedHandleFunction) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nragent.InstrumentMain, nrnethttp.WrapNestedHandleFunction) assert.Equal(t, tt.expect, got) }) } @@ -1535,14 +1539,14 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - got := testStatelessTracingFunction(t, tt.code, InstrumentHandleFunction) + defer parser.PanicRecovery(t) + got := parser.RunStatelessTracingFunction(t, tt.code, nrnethttp.InstrumentHandleFunction) assert.Equal(t, tt.expect, got) }) } } -func Test_getHTTPRequestArgNameDecl(t *testing.T) { +func TestGetHTTPRequestArgNameDecl(t *testing.T) { tests := []struct { name string fn *dst.FuncDecl @@ -1561,7 +1565,7 @@ func Test_getHTTPRequestArgNameDecl(t *testing.T) { }, Type: &dst.Ident{ Name: "ResponseWriter", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, 1: { @@ -1573,7 +1577,7 @@ func Test_getHTTPRequestArgNameDecl(t *testing.T) { Type: &dst.StarExpr{ X: &dst.Ident{ Name: "Request", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, }, @@ -1596,7 +1600,7 @@ func Test_getHTTPRequestArgNameDecl(t *testing.T) { }, Type: &dst.Ident{ Name: "ResponseWriter", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, 1: { @@ -1608,7 +1612,7 @@ func Test_getHTTPRequestArgNameDecl(t *testing.T) { Type: &dst.StarExpr{ X: &dst.Ident{ Name: "Request", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, }, @@ -1631,7 +1635,7 @@ func Test_getHTTPRequestArgNameDecl(t *testing.T) { }, Type: &dst.Ident{ Name: "ResponseWriter", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, 1: { @@ -1643,7 +1647,7 @@ func Test_getHTTPRequestArgNameDecl(t *testing.T) { Type: &dst.StarExpr{ X: &dst.Ident{ Name: "Request", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, }, @@ -1656,7 +1660,7 @@ func Test_getHTTPRequestArgNameDecl(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ok, got := getHTTPRequestArgName(tt.fn) + ok, got := nrnethttp.GetHTTPRequestArgName(tt.fn) if !ok || got != tt.expect { t.Errorf("got %v, want %v", got, tt.expect) } @@ -1664,7 +1668,7 @@ func Test_getHTTPRequestArgNameDecl(t *testing.T) { } } -func Test_getHTTPRequestArgNameLit(t *testing.T) { +func TestGetHTTPRequestArgNameLit(t *testing.T) { tests := []struct { name string fn *dst.FuncLit @@ -1683,7 +1687,7 @@ func Test_getHTTPRequestArgNameLit(t *testing.T) { }, Type: &dst.Ident{ Name: "ResponseWriter", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, 1: { @@ -1695,7 +1699,7 @@ func Test_getHTTPRequestArgNameLit(t *testing.T) { Type: &dst.StarExpr{ X: &dst.Ident{ Name: "Request", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, }, @@ -1718,7 +1722,7 @@ func Test_getHTTPRequestArgNameLit(t *testing.T) { }, Type: &dst.Ident{ Name: "ResponseWriter", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, 1: { @@ -1730,7 +1734,7 @@ func Test_getHTTPRequestArgNameLit(t *testing.T) { Type: &dst.StarExpr{ X: &dst.Ident{ Name: "Request", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, }, @@ -1753,7 +1757,7 @@ func Test_getHTTPRequestArgNameLit(t *testing.T) { }, Type: &dst.Ident{ Name: "ResponseWriter", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, 1: { @@ -1765,7 +1769,7 @@ func Test_getHTTPRequestArgNameLit(t *testing.T) { Type: &dst.StarExpr{ X: &dst.Ident{ Name: "Request", - Path: codegen.HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, }, @@ -1778,7 +1782,7 @@ func Test_getHTTPRequestArgNameLit(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ok, got := getHTTPRequestArgName(tt.fn) + ok, got := nrnethttp.GetHTTPRequestArgName(tt.fn) if !ok || got != tt.expect { t.Errorf("got %v, want %v", got, tt.expect) } @@ -1836,30 +1840,30 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - id, err := pseudo_uuid() + defer parser.PanicRecovery(t) + id, err := parser.Pseudo_uuid() if err != nil { t.Fatal(err) } testDir := fmt.Sprintf("tmp_%s", id) - defer cleanTestApp(t, testDir) + defer parser.CleanTestApp(t, testDir) - manager := testInstrumentationManager(t, tt.code, testDir) - pkg := manager.getDecoratorPackage() + manager := parser.TestInstrumentationManager(t, tt.code, testDir) + pkg := manager.GetDecoratorPackage() if pkg == nil { - t.Fatalf("Package was nil: %+v", manager.packages) + t.Fatalf("Package was nil") } // First populate the function cache for _, decl := range pkg.Syntax[0].Decls { if fn, ok := decl.(*dst.FuncDecl); ok { - manager.transactionCache.Functions[fn.Name.Name] = fn + manager.TransactionCache().Functions[fn.Name.Name] = fn } } // Load pre-instrumentation tracing to populate transaction cache - manager.loadPreInstrumentationTracingFunctions(DetectWrappedRoutes) + manager.LoadPreInstrumentationTracingFunctions(nrnethttp.DetectWrappedRoutes) err = manager.ScanApplication() if err != nil { t.Fatalf("Failed to scan application: %v", err) @@ -1871,7 +1875,7 @@ func main() { t.Fatal("code must contain a function declaration at position 1") } - got := HandlerIsInstrumented(manager, decl) + got := nrnethttp.HandlerIsInstrumented(manager, decl) if got != tt.expect { t.Errorf("HandlerIsInstrumented() = %v, want %v", got, tt.expect) } @@ -1983,30 +1987,30 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - id, err := pseudo_uuid() + defer parser.PanicRecovery(t) + id, err := parser.Pseudo_uuid() if err != nil { t.Fatal(err) } testDir := fmt.Sprintf("tmp_%s", id) - defer cleanTestApp(t, testDir) + defer parser.CleanTestApp(t, testDir) - manager := testInstrumentationManager(t, tt.code, testDir) - pkg := manager.getDecoratorPackage() + manager := parser.TestInstrumentationManager(t, tt.code, testDir) + pkg := manager.GetDecoratorPackage() if pkg == nil { - t.Fatalf("Package was nil: %+v", manager.packages) + t.Fatalf("Package was nil") } // First populate the function cache by scanning all declarations for _, decl := range pkg.Syntax[0].Decls { if fn, ok := decl.(*dst.FuncDecl); ok { - manager.transactionCache.Functions[fn.Name.Name] = fn + manager.TransactionCache().Functions[fn.Name.Name] = fn } } - // Run DetectWrappedRoutes - manager.loadPreInstrumentationTracingFunctions(DetectWrappedRoutes) + // Run nrnethttp.DetectWrappedRoutes + manager.LoadPreInstrumentationTracingFunctions(nrnethttp.DetectWrappedRoutes) err = manager.ScanApplication() if err != nil { t.Fatalf("Failed to scan application: %v", err) @@ -2015,7 +2019,7 @@ func main() { // Check if the handler is in the transaction cache if tt.handlerName != "" { found := false - for ident := range manager.transactionCache.Transactions { + for ident := range manager.TransactionCache().Transactions { if ident.Name == tt.handlerName { found = true break @@ -2084,37 +2088,37 @@ func main() { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - id, err := pseudo_uuid() + defer parser.PanicRecovery(t) + id, err := parser.Pseudo_uuid() if err != nil { t.Fatal(err) } testDir := fmt.Sprintf("tmp_%s", id) - defer cleanTestApp(t, testDir) + defer parser.CleanTestApp(t, testDir) - manager := testInstrumentationManager(t, tt.code, testDir) - pkg := manager.getDecoratorPackage() + manager := parser.TestInstrumentationManager(t, tt.code, testDir) + pkg := manager.GetDecoratorPackage() if pkg == nil { - t.Fatalf("Package was nil: %+v", manager.packages) + t.Fatalf("Package was nil") } // First populate the function cache for _, decl := range pkg.Syntax[0].Decls { if fn, ok := decl.(*dst.FuncDecl); ok { - manager.transactionCache.Functions[fn.Name.Name] = fn + manager.TransactionCache().Functions[fn.Name.Name] = fn } } // First detect wrapped routes to populate the cache - manager.loadPreInstrumentationTracingFunctions(DetectWrappedRoutes) + manager.LoadPreInstrumentationTracingFunctions(nrnethttp.DetectWrappedRoutes) err = manager.ScanApplication() if err != nil { t.Fatalf("Failed to scan application: %v", err) } // Now instrument handlers - manager.tracingFunctions.stateless = append(manager.tracingFunctions.stateless, InstrumentHandleFunction) + manager.LoadStatelessTracingFunctions(nrnethttp.InstrumentHandleFunction) err = manager.TracePackageCalls() if err != nil { t.Fatalf("Failed to trace package calls: %v", err) diff --git a/internal/codegen/slog.go b/integrations/nrslog/codegen.go similarity index 98% rename from internal/codegen/slog.go rename to integrations/nrslog/codegen.go index 4df2b8fa..ddca0a86 100644 --- a/internal/codegen/slog.go +++ b/integrations/nrslog/codegen.go @@ -1,4 +1,4 @@ -package codegen +package nrslog import ( "go/token" diff --git a/end-to-end-tests/slog-examples/expect.ref b/integrations/nrslog/example/slog-examples/expect.ref similarity index 100% rename from end-to-end-tests/slog-examples/expect.ref rename to integrations/nrslog/example/slog-examples/expect.ref diff --git a/end-to-end-tests/slog-examples/main.go b/integrations/nrslog/example/slog-examples/main.go similarity index 100% rename from end-to-end-tests/slog-examples/main.go rename to integrations/nrslog/example/slog-examples/main.go diff --git a/parser/slog.go b/integrations/nrslog/slog.go similarity index 89% rename from parser/slog.go rename to integrations/nrslog/slog.go index 7c9ee907..7c50709e 100644 --- a/parser/slog.go +++ b/integrations/nrslog/slog.go @@ -1,11 +1,11 @@ -package parser +package nrslog import ( "slices" "github.com/dave/dst" "github.com/dave/dst/dstutil" - "github.com/newrelic/go-easy-instrumentation/internal/codegen" + "github.com/newrelic/go-easy-instrumentation/parser" ) const ( @@ -36,7 +36,7 @@ func slogMiddlewareCall(stmt dst.Stmt) string { // InstrumentSlogHandler will check to see if any slog.NewTextHandler calls are made within the main function // NOTE: Should we be limiting this to main? Is it possible/widely accepted to initialize a logging library outside of main? -func InstrumentSlogHandler(manager *InstrumentationManager, c *dstutil.Cursor) { +func InstrumentSlogHandler(manager *parser.InstrumentationManager, c *dstutil.Cursor) { mainFunctionNode := c.Node() if decl, ok := mainFunctionNode.(*dst.FuncDecl); ok { if decl.Name.Name != "main" { @@ -53,9 +53,9 @@ func InstrumentSlogHandler(manager *InstrumentationManager, c *dstutil.Cursor) { // We detected an slog handler nrHandler := "NR" + slogHandler handlerNames = append(handlerNames, slogHandler) - middleware, goGet := codegen.SlogHandlerWrapper(slogHandler, nrHandler) + middleware, goGet := SlogHandlerWrapper(slogHandler, nrHandler) decl.Body.List = append(decl.Body.List[:i+1], append([]dst.Stmt{middleware}, decl.Body.List[i+1:]...)...) - manager.addImport(goGet) + manager.AddImport(goGet) i++ } else if len(handlerNames) > 0 { // Translate any handlers we've seen so far to our wrapped ones diff --git a/internal/codegen/codegen.go b/internal/codegen/codegen.go index 3396a889..fe96e61a 100644 --- a/internal/codegen/codegen.go +++ b/internal/codegen/codegen.go @@ -14,3 +14,8 @@ // may result in a panic, which is not an acceptable outcome. A test to verify that the output is what // we expect is a good safegard. package codegen + +const ( + // NewRelicAgentImportPath is the import path for the New Relic Go agent + NewRelicAgentImportPath = "github.com/newrelic/go-agent/v3/newrelic" +) diff --git a/internal/codegen/context.go b/internal/codegen/context.go index ab62b939..33a41341 100644 --- a/internal/codegen/context.go +++ b/internal/codegen/context.go @@ -52,3 +52,18 @@ func NewContextParameter(name string) *dst.Field { }, } } + +// HttpRequestContext creates an expression for calling the Context() method on an HTTP request +// Used to extract context from http.Request objects +func HttpRequestContext(reqArgName string) dst.Expr { + return &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: &dst.Ident{ + Name: reqArgName, + }, + Sel: &dst.Ident{ + Name: "Context", + }, + }, + } +} diff --git a/internal/codegen/segment_test.go b/internal/codegen/segment_test.go index cc6f2177..395b5e3a 100644 --- a/internal/codegen/segment_test.go +++ b/internal/codegen/segment_test.go @@ -1,4 +1,4 @@ -package codegen +package codegen_test import ( "go/token" @@ -6,10 +6,12 @@ import ( "testing" "github.com/dave/dst" + "github.com/newrelic/go-easy-instrumentation/integrations/nrnethttp" + "github.com/newrelic/go-easy-instrumentation/internal/codegen" "github.com/stretchr/testify/assert" ) -func Test_endExternalSegment(t *testing.T) { +func TestEndExternalSegment(t *testing.T) { type args struct { segmentName string nodeDecs *dst.NodeDecs @@ -47,7 +49,7 @@ func Test_endExternalSegment(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := EndExternalSegment(tt.args.segmentName, tt.args.nodeDecs); !reflect.DeepEqual(got, tt.want) { + if got := codegen.EndExternalSegment(tt.args.segmentName, tt.args.nodeDecs); !reflect.DeepEqual(got, tt.want) { t.Errorf("endExternalSegment() = %v, want %v", got, tt.want) } if len(tt.args.nodeDecs.End) != 0 { @@ -60,7 +62,7 @@ func Test_endExternalSegment(t *testing.T) { } } -func Test_startExternalSegment(t *testing.T) { +func TestStartExternalSegment(t *testing.T) { type args struct { request dst.Expr txnVar dst.Expr @@ -75,7 +77,7 @@ func Test_startExternalSegment(t *testing.T) { { name: "start_external_segment", args: args{ - request: &dst.Ident{Name: "r", Path: HttpImportPath}, + request: &dst.Ident{Name: "r", Path: nrnethttp.HttpImportPath}, txnVar: dst.NewIdent("txn"), segmentVar: "example", nodeDecs: &dst.NodeDecs{ @@ -92,11 +94,11 @@ func Test_startExternalSegment(t *testing.T) { &dst.CallExpr{ Fun: &dst.Ident{ Name: "StartExternalSegment", - Path: NewRelicAgentImportPath, + Path: codegen.NewRelicAgentImportPath, }, Args: []dst.Expr{ dst.NewIdent("txn"), - dst.Clone(&dst.Ident{Name: "r", Path: HttpImportPath}).(dst.Expr), + dst.Clone(&dst.Ident{Name: "r", Path: nrnethttp.HttpImportPath}).(dst.Expr), }, }, }, @@ -111,7 +113,7 @@ func Test_startExternalSegment(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := StartExternalSegment(tt.args.request, tt.args.txnVar, tt.args.segmentVar, tt.args.nodeDecs); !reflect.DeepEqual(got, tt.want) { + if got := codegen.StartExternalSegment(tt.args.request, tt.args.txnVar, tt.args.segmentVar, tt.args.nodeDecs); !reflect.DeepEqual(got, tt.want) { t.Errorf("startExternalSegment() = %v, want %v", got, tt.want) } if len(tt.args.nodeDecs.Start) != 0 { @@ -124,7 +126,7 @@ func Test_startExternalSegment(t *testing.T) { } } -func Test_captureHttpResponse(t *testing.T) { +func TestCaptureHttpResponse(t *testing.T) { type args struct { segmentVariable string responseVariable dst.Expr @@ -140,7 +142,7 @@ func Test_captureHttpResponse(t *testing.T) { segmentVariable: "example", responseVariable: &dst.Ident{ Name: "resp", - Path: HttpImportPath, + Path: nrnethttp.HttpImportPath, }, }, want: &dst.AssignStmt{ @@ -153,7 +155,7 @@ func Test_captureHttpResponse(t *testing.T) { Rhs: []dst.Expr{ dst.Clone(&dst.Ident{ Name: "resp", - Path: HttpImportPath, + Path: nrnethttp.HttpImportPath, }).(dst.Expr), }, Tok: token.ASSIGN, @@ -162,14 +164,14 @@ func Test_captureHttpResponse(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := CaptureHttpResponse(tt.args.segmentVariable, tt.args.responseVariable); !reflect.DeepEqual(got, tt.want) { + if got := codegen.CaptureHttpResponse(tt.args.segmentVariable, tt.args.responseVariable); !reflect.DeepEqual(got, tt.want) { t.Errorf("captureHttpResponse() = %v, want %v", got, tt.want) } }) } } -func Test_deferSegment(t *testing.T) { +func TestDeferSegment(t *testing.T) { type args struct { segmentName string txnVarName string @@ -217,7 +219,7 @@ func Test_deferSegment(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := DeferSegment(tt.args.segmentName, dst.NewIdent(tt.args.txnVarName)) + got := codegen.DeferSegment(tt.args.segmentName, dst.NewIdent(tt.args.txnVarName)) assert.Equal(t, tt.want, got) }) } diff --git a/internal/codegen/transaction_test.go b/internal/codegen/transaction_test.go index 41483c76..c85e8797 100644 --- a/internal/codegen/transaction_test.go +++ b/internal/codegen/transaction_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_startTransaction(t *testing.T) { +func TestStartTransaction(t *testing.T) { type args struct { appVariableName string transactionVariableName string @@ -83,7 +83,7 @@ func Test_startTransaction(t *testing.T) { } } -func Test_endTransaction(t *testing.T) { +func TestEndTransaction(t *testing.T) { type args struct { transactionVariableName string } @@ -115,7 +115,7 @@ func Test_endTransaction(t *testing.T) { } } -func Test_NewTransactionParameter(t *testing.T) { +func TestNewTransactionParameter(t *testing.T) { type args struct { txnName string } @@ -152,7 +152,7 @@ func Test_NewTransactionParameter(t *testing.T) { } } -func Test_txnNewGoroutine(t *testing.T) { +func TestTxnNewGoroutine(t *testing.T) { type args struct { txnVarName string } @@ -186,7 +186,7 @@ func Test_txnNewGoroutine(t *testing.T) { } } -func Test_generateNoticeError(t *testing.T) { +func TestGenerateNoticeError(t *testing.T) { type args struct { errExpr dst.Expr txnName string @@ -286,19 +286,19 @@ func Test_generateNoticeError(t *testing.T) { func TestGetApplication(t *testing.T) { tests := []struct { - name string + name string transactionVariableExpression dst.Expr - wantTxnVarName string + wantTxnVarName string }{ { - name: "generates Application() call", + name: "generates Application() call", transactionVariableExpression: dst.NewIdent("txn"), - wantTxnVarName: "txn", + wantTxnVarName: "txn", }, { - name: "generates Application() call with custom name", + name: "generates Application() call with custom name", transactionVariableExpression: dst.NewIdent("myTransaction"), - wantTxnVarName: "myTransaction", + wantTxnVarName: "myTransaction", }, } diff --git a/internal/util/typeInfo_test.go b/internal/util/typeInfo_test.go index 32511bd7..6c8bb5be 100644 --- a/internal/util/typeInfo_test.go +++ b/internal/util/typeInfo_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func Test_isNamedError(t *testing.T) { +func TestIsNamedError(t *testing.T) { type args struct { n types.Type } @@ -45,7 +45,7 @@ func Test_isNamedError(t *testing.T) { } } -func Test_checkUnderlyingType(t *testing.T) { +func TestCheckUnderlyingType(t *testing.T) { const grpcServerStreamType = "google.golang.org/grpc.ServerStream" type args struct { diff --git a/parser/README.md b/parser/README.md index 50043557..421664a6 100644 --- a/parser/README.md +++ b/parser/README.md @@ -181,6 +181,6 @@ There are a few best practices that should always be followed when contributing 1. Due to the complex nature of what we are testing, unit tests will always be missing something. For this reason, it is best to keep the scope of the unit tests small, and focus on enforcing a set of expected behaviors on a function. This will help us protect against regressions as we add more features and modify the code in the future. -2. Always cover new instrumentation with [end to end tests](/end-to-end-tests/README.md). These are the most robust way to catch edge cases, and are a non-negotiable requirement for every feature added. +2. Always cover new instrumentation with [validation tests](/validation-tests/README.md). These are the most robust way to catch edge cases, and are a non-negotiable requirement for every feature added. 3. All code generation functionality should be written as an exported function in internal/codegen. This allows us to re-use that code across the application mroe easily. 4. Use `internal/util.TypeOf` to type check things rather than using DST node paths when possible. This is more reliable, and accurate. \ No newline at end of file diff --git a/parser/errors.go b/parser/errors.go new file mode 100644 index 00000000..98cd9d47 --- /dev/null +++ b/parser/errors.go @@ -0,0 +1,202 @@ +package parser + +import ( + "fmt" + "go/token" + "slices" + + "github.com/dave/dst" + "github.com/dave/dst/decorator" + "github.com/dave/dst/dstutil" + "github.com/newrelic/go-easy-instrumentation/internal/codegen" + "github.com/newrelic/go-easy-instrumentation/internal/comment" + "github.com/newrelic/go-easy-instrumentation/internal/util" + "github.com/newrelic/go-easy-instrumentation/parser/tracestate" +) + +const ( + untypedNil = "untyped nil" +) + +// errNilCheck tests if an if statement contains a conditional check that an error is not nil +func errNilCheck(stmt *dst.BinaryExpr, pkg *decorator.Package) bool { + exprTypeX := util.TypeOf(stmt.X, pkg) + if exprTypeX == nil { + return false + } + + exprTypeY := util.TypeOf(stmt.Y, pkg) + if exprTypeY == nil { + return false + } + + // If the left side contains a nested error that checks err != nil, then return true + nestedX, okX := stmt.X.(*dst.BinaryExpr) + if okX && errNilCheck(nestedX, pkg) { + return true + } + + // If the right side contains a nested error that checks err != nil, then return true + nestedY, okY := stmt.Y.(*dst.BinaryExpr) + if okY && errNilCheck(nestedY, pkg) { + return true + } + + // base case: this is a single binary expression + if stmt.Op != token.NEQ { + return false + } + + if util.IsError(exprTypeX) && exprTypeY.String() == untypedNil { + return true + } + + if util.IsError(exprTypeY) && exprTypeX.String() == untypedNil { + return true + } + return false +} + +func shouldNoticeError(stmt dst.Stmt, pkg *decorator.Package, tracing *tracestate.State) bool { + ifStmt, ok := stmt.(*dst.IfStmt) + if !ok { + return false + } + binExpr, ok := ifStmt.Cond.(*dst.BinaryExpr) + if ok && errNilCheck(binExpr, pkg) { + return true + } + + return shouldNoticeError(ifStmt.Else, pkg, tracing) +} + +func findErrorVariable(stmt *dst.AssignStmt, pkg *decorator.Package) dst.Expr { + for _, variable := range stmt.Lhs { + t := util.TypeOf(variable, pkg) + if t == nil { + continue + } + + // ignore blank identifiers + ident, ok := variable.(*dst.Ident) + if ok && ident.Name == "_" { + continue + } + + // if the variable is an error type, return it + if util.IsError(t) { + return variable + } + } + return nil +} + +// StatefulTracingFunction +// NoticeError will check for the presence of an error.Error variable in the body at the index in bodyIndex. +// If it finds that an error is returned, it will add a line after the assignment statement to capture an error +// with a newrelic transaction. All transactions are assumed to be named "txn" +func NoticeError(manager *InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State, functionCallWasTraced bool) bool { + if tracing.IsMain() { + return false + } + + pkg := manager.GetDecoratorPackage() + switch nodeVal := stmt.(type) { + case *dst.ReturnStmt: + if functionCallWasTraced || c.Index() < 0 { + return false + } + for i, result := range nodeVal.Results { + call, ok := result.(*dst.CallExpr) + if ok { + newSmts, retVals := codegen.CaptureErrorReturnCallExpression(pkg, call, tracing.TransactionVariable()) + if newSmts == nil { + return false + } + + comment.Debug(pkg, stmt, "Capturing error return value for NoticeError") + + // add an empty line beore the return statement for readability + nodeVal.Decorations().Before = dst.EmptyLine + + // if this is the first element in the slice, it will be the top of the function + if c.Index() == 0 { + newSmts[0].Decorations().Before = dst.NewLine + } + + for _, stmt := range newSmts { + c.InsertBefore(stmt) + } + + nodeVal.Results = slices.Delete(nodeVal.Results, i, i+1) + nodeVal.Results = slices.Insert(nodeVal.Results, i, retVals...) + } + cachedExpr := manager.ErrorCache().GetExpression() + if cachedExpr != nil && util.AssertExpressionEqual(result, cachedExpr) { + manager.ErrorCache().Clear() + comment.Debug(pkg, stmt, "Injecting error nil check with NoticeError before return") + capture := codegen.IfErrorNotNilNoticeError(cachedExpr, tracing.TransactionVariable()) + capture.Decs.Before = dst.EmptyLine + c.InsertBefore(capture) + return true + } + } + case *dst.IfStmt: + if nodeVal.Init != nil { + NoticeError(manager, nodeVal.Init, c, tracing, functionCallWasTraced) + } + if shouldNoticeError(stmt, pkg, tracing) { + errExpr := manager.ErrorCache().GetExpression() + if errExpr != nil { + var stmtBlock dst.Stmt + if nodeVal.Body != nil && len(nodeVal.Body.List) > 0 { + stmtBlock = nodeVal.Body.List[0] + } + comment.Debug(pkg, stmt, "Injecting NoticeError into error handling block") + nodeVal.Body.List = append([]dst.Stmt{codegen.NoticeError(errExpr, tracing.TransactionVariable(), stmtBlock)}, nodeVal.Body.List...) + manager.ErrorCache().Clear() + return true + } + } + case *dst.AssignStmt: + if c.Index() < 0 { + return false + } + + // avoid capturing errors that were already captured upstream + if functionCallWasTraced { + return false + } + // if the call was traced, ignore the assigned error because it will be captured in the upstream + // function body + errExpr := findErrorVariable(nodeVal, pkg) + if errExpr == nil { + return false + } + + cachedErrExpr := manager.ErrorCache().GetExpression() + if cachedErrExpr != nil { + stmt := manager.ErrorCache().GetStatement() + comment.Warn(pkg, stmt, stmt, fmt.Sprintf("Unchecked Error \"%s\", please consult New Relic documentation on error capture", util.WriteExpr(cachedErrExpr, pkg))) + manager.ErrorCache().Clear() + } + + // Always load the error into the cache + var errStmt dst.Stmt + errStmt = nodeVal + + // its possible that this error is not in a block statment + // if thats the case, we should attempt to add our comment to something that is. + if c.Index() < 0 { + parent := c.Parent() + parentStmt, ok := parent.(dst.Stmt) + if ok { + errStmt = parentStmt + } + } + if !manager.ErrorCache().IsExistingError(errExpr) { + manager.ErrorCache().Load(errExpr, errStmt) + } + } + return false +} diff --git a/parser/grpc_internal_test.go b/parser/grpc_internal_test.go new file mode 100644 index 00000000..a91bb108 --- /dev/null +++ b/parser/grpc_internal_test.go @@ -0,0 +1,242 @@ +// grpc_internal_test.go contains tests for gRPC integration that require access +// to InstrumentationManager internal state for construction. +package parser_test + +import ( + "go/ast" + "go/token" + "go/types" + "testing" + + "github.com/dave/dst" + "github.com/dave/dst/decorator" + "github.com/newrelic/go-easy-instrumentation/integrations/nrgrpc" + "github.com/newrelic/go-easy-instrumentation/internal/codegen" + "github.com/newrelic/go-easy-instrumentation/parser" + "github.com/newrelic/go-easy-instrumentation/parser/facts" + "github.com/newrelic/go-easy-instrumentation/parser/tracestate/traceobject" + "github.com/stretchr/testify/assert" + "golang.org/x/tools/go/packages" +) + +func TestGetTxnFromGrpcServer(t *testing.T) { + grpcServerStreamType := types.NewNamed( + types.NewTypeName(0, types.NewPackage("github.com/example/testapp", "testapp"), "TestApp_StreamServer", nil), // Main Type + types.NewInterfaceType( // Underlying Type + nil, + []types.Type{ + types.NewNamed( + types.NewTypeName(0, types.NewPackage("google.golang.org/grpc", "grpc"), "ServerStream", nil), + nil, + nil, + ), + }, + ), + nil, + ) + + contextParamName := &dst.Ident{Name: "ctx"} + astContext := &ast.Ident{Name: "ctx"} + serverStreamParamName := &dst.Ident{Name: "stream"} + astServerStream := &ast.Ident{Name: "stream"} + manager := &parser.InstrumentationManager{} + manager.SetCurrentPackage("test") + manager.SetPackageState("test", &parser.PackageState{ + Pkg: &decorator.Package{ + Package: &packages.Package{ + TypesInfo: &types.Info{ + Types: map[ast.Expr]types.TypeAndValue{ + astContext: { + Type: types.NewNamed(types.NewTypeName(token.NoPos, types.NewPackage("context", "context"), "Context", nil), nil, nil), + }, + astServerStream: { + Type: grpcServerStreamType, + }, + }, + }, + }, + Decorator: &decorator.Decorator{ + Map: decorator.Map{ + Ast: decorator.AstMap{ + Nodes: map[dst.Node]ast.Node{contextParamName: astContext, serverStreamParamName: astServerStream}, + }, + }, + }, + }, + }) + manager.SetFacts(facts.Keeper{ + "github.com/example/testapp.TestApp_StreamServer": facts.GrpcServerStream, + }) + + type args struct { + manager *parser.InstrumentationManager + params []*dst.Field + } + tests := []struct { + name string + args + want *nrgrpc.GrpcServerTxnData + expect bool + }{ + { + name: "grpc server stream", + args: args{ + manager: manager, + params: []*dst.Field{ + { + Names: []*dst.Ident{serverStreamParamName}, + }, + }, + }, + want: &nrgrpc.GrpcServerTxnData{ + TxnAssignment: codegen.TxnFromContext("txn", nrgrpc.GrpcStreamContext(serverStreamParamName)), + TraceObject: traceobject.NewTransaction(), + }, + expect: true, + }, + { + name: "grpc context", + args: args{ + manager: manager, + params: []*dst.Field{ + { + Names: []*dst.Ident{contextParamName}, + }, + }, + }, + want: &nrgrpc.GrpcServerTxnData{ + TraceObject: traceobject.NewContext(contextParamName.Name), + }, + expect: true, + }, + { + name: "empty params", + args: args{ + manager: manager, + params: []*dst.Field{}, + }, + want: nil, + expect: false, + }, + { + name: "no context or stream", + args: args{ + manager: manager, + params: []*dst.Field{ + { + Names: []*dst.Ident{ + {Name: "notContext"}, + }, + }, + }, + }, + want: nil, + expect: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := nrgrpc.GetTxnFromGrpcServer(tt.args.manager, tt.args.params, "txn") + if tt.expect { + if !ok { + t.Error("expected a transaction to be gotten from grpc server agrument") + } else { + assert.Equal(t, tt.want, got) + } + } else { + if ok { + t.Errorf("expected no transaction to be gotten from grpc server agrument, but got %+v", got) + } + } + }) + } +} + +func TestIsGrpcServerMethod(t *testing.T) { + serverRecv := &dst.Ident{ + Name: "srv", + } + astServer := &ast.Ident{ + Name: "srv", + } + + manager := &parser.InstrumentationManager{} + manager.SetCurrentPackage("test") + manager.SetPackageState("test", &parser.PackageState{ + Pkg: &decorator.Package{ + Package: &packages.Package{ + TypesInfo: &types.Info{ + Types: map[ast.Expr]types.TypeAndValue{ + astServer: { + Type: types.NewPointer(types.NewNamed(types.NewTypeName(token.NoPos, types.NewPackage("github.com/example/testapp", "testapp"), "Server", types.NewInterfaceType(nil, nil)), nil, nil)), + }, + }, + }, + }, + Decorator: &decorator.Decorator{ + Map: decorator.Map{ + Ast: decorator.AstMap{ + Nodes: map[dst.Node]ast.Node{serverRecv: astServer}, + }, + }, + }, + }, + }) + manager.SetFacts(facts.Keeper{ + "*github.com/example/testapp.Server": facts.GrpcServerType, + }) + + type args struct { + manager *parser.InstrumentationManager + decl *dst.FuncDecl + } + tests := []struct { + name string + args + want bool + }{ + { + name: "grpc server method", + args: args{ + manager: manager, + decl: &dst.FuncDecl{ + Recv: &dst.FieldList{ + List: []*dst.Field{ + { + Names: []*dst.Ident{serverRecv}, + }, + }, + }, + }, + }, + want: true, + }, + { + name: "reciever is not grpc server", + args: args{ + manager: manager, + decl: &dst.FuncDecl{ + Recv: &dst.FieldList{ + List: []*dst.Field{ + { + Names: []*dst.Ident{ + { + Name: "notServer", + }, + }, + }, + }, + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := nrgrpc.IsGrpcServerMethod(tt.args.manager, tt.args.decl); got != tt.want { + t.Errorf("isGrpcServerMethod() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/parser/manager.go b/parser/manager.go index f338dbff..4edb26c7 100644 --- a/parser/manager.go +++ b/parser/manager.go @@ -90,28 +90,23 @@ func NewInstrumentationManager(pkgs []*decorator.Package, appName, agentVariable return manager } -// DetectDependencyIntegrations -func (m *InstrumentationManager) DetectDependencyIntegrations() error { - m.loadPreInstrumentationTracingFunctions(DetectTransactions, DetectErrors, DetectWrappedRoutes) - m.loadStatelessTracingFunctions(InstrumentMain, InstrumentHandleFunction, InstrumentHttpClient, CannotInstrumentHttpMethod, InstrumentGrpcDial, InstrumentGinFunction, InstrumentGrpcServerMethod, InstrumentSlogHandler) - m.loadStatefulTracingFunctions(ExternalHttpCall, WrapNestedHandleFunction, InstrumentGrpcServer, InstrumentGinMiddleware, InstrumentChiMiddleware, InstrumentChiRouterLiteral) - m.loadDependencyScans(FindGrpcServerObject) - return nil -} - -func (m *InstrumentationManager) loadPreInstrumentationTracingFunctions(functions ...PreInstrumentationTracingFunction) { +// LoadPreInstrumentationTracingFunctions registers pre-instrumentation tracing functions (exported for cmd) +func (m *InstrumentationManager) LoadPreInstrumentationTracingFunctions(functions ...PreInstrumentationTracingFunction) { m.tracingFunctions.preinstrumentation = append(m.tracingFunctions.preinstrumentation, functions...) } -func (m *InstrumentationManager) loadStatefulTracingFunctions(functions ...StatefulTracingFunction) { +// LoadStatefulTracingFunctions registers stateful tracing functions (exported for cmd) +func (m *InstrumentationManager) LoadStatefulTracingFunctions(functions ...StatefulTracingFunction) { m.tracingFunctions.stateful = append(m.tracingFunctions.stateful, functions...) } -func (m *InstrumentationManager) loadStatelessTracingFunctions(functions ...StatelessTracingFunction) { +// LoadStatelessTracingFunctions registers stateless tracing functions (exported for cmd) +func (m *InstrumentationManager) LoadStatelessTracingFunctions(functions ...StatelessTracingFunction) { m.tracingFunctions.stateless = append(m.tracingFunctions.stateless, functions...) } -func (m *InstrumentationManager) loadDependencyScans(scans ...FactDiscoveryFunction) { +// LoadDependencyScans registers fact discovery functions (exported for cmd) +func (m *InstrumentationManager) LoadDependencyScans(scans ...FactDiscoveryFunction) { m.tracingFunctions.dependency = append(m.tracingFunctions.dependency, scans...) } @@ -128,7 +123,8 @@ func (m *InstrumentationManager) setPackage(pkgName string) { m.currentPackage = pkgName } -func (m *InstrumentationManager) addImport(path string) { +// AddImport adds an import path to the current package (exported for integrations) +func (m *InstrumentationManager) AddImport(path string) { if path == "" { return } @@ -138,6 +134,56 @@ func (m *InstrumentationManager) addImport(path string) { } } +// addImport is the internal version that calls AddImport +func (m *InstrumentationManager) addImport(path string) { + m.AddImport(path) +} + +// AgentVariableName returns the New Relic agent variable name (exported for integrations) +func (m *InstrumentationManager) AgentVariableName() string { + return m.agentVariableName +} + +// SetAgentVariableName sets the New Relic agent variable name (exported for integrations) +func (m *InstrumentationManager) SetAgentVariableName(name string) { + m.agentVariableName = name +} + +// GetDecoratorPackage returns the decorator package for the current package (exported for integrations) +func (m *InstrumentationManager) GetDecoratorPackage() *decorator.Package { + return m.getDecoratorPackage() +} + +// Facts returns the facts keeper (exported for integrations) +func (m *InstrumentationManager) Facts() *facts.Keeper { + return &m.facts +} + +// AppName returns the application name (exported for integrations) +func (m *InstrumentationManager) AppName() string { + return m.appName +} + +// SetupFunc returns the setup function declaration (exported for integrations) +func (m *InstrumentationManager) SetupFunc() *dst.FuncDecl { + return m.setupFunc +} + +// SetSetupFunc sets the setup function declaration (exported for integrations) +func (m *InstrumentationManager) SetSetupFunc(fn *dst.FuncDecl) { + m.setupFunc = fn +} + +// ErrorCache returns the error cache (exported for integrations) +func (m *InstrumentationManager) ErrorCache() *errorcache.ErrorCache { + return &m.errorCache +} + +// TransactionCache returns the transaction cache (exported for integrations) +func (m *InstrumentationManager) TransactionCache() *transactioncache.TransactionCache { + return &m.transactionCache +} + func (m *InstrumentationManager) getImports() []string { i := 0 state, ok := m.packages[m.currentPackage] @@ -646,3 +692,34 @@ func (m *InstrumentationManager) ResolveUnitTests() error { return nil } + +// Test helper methods - only for use in tests to construct managers with specific state + +// SetCurrentPackage sets the current package (for tests only) +func (m *InstrumentationManager) SetCurrentPackage(pkg string) { + m.currentPackage = pkg +} + +// SetPackageState sets package state for a specific package ID (for tests only) +func (m *InstrumentationManager) SetPackageState(pkgID string, state *PackageState) { + if m.packages == nil { + m.packages = make(map[string]*packageState) + } + m.packages[pkgID] = &packageState{ + pkg: state.Pkg, + tracedFuncs: state.TracedFuncs, + importsAdded: state.ImportsAdded, + } +} + +// SetFacts sets the facts keeper (for tests only) +func (m *InstrumentationManager) SetFacts(f facts.Keeper) { + m.facts = f +} + +// PackageState holds state for a single package (exported for test construction) +type PackageState struct { + Pkg *decorator.Package + TracedFuncs map[string]*tracedFunctionDecl + ImportsAdded map[string]bool +} diff --git a/parser/manager_test.go b/parser/manager_test.go index ecee33e6..c8198bb3 100644 --- a/parser/manager_test.go +++ b/parser/manager_test.go @@ -14,7 +14,7 @@ import ( "golang.org/x/tools/go/packages" ) -func Test_AddImport(t *testing.T) { +func TestAddImport(t *testing.T) { type fields struct { userAppPath string diffFile string @@ -61,7 +61,7 @@ func Test_AddImport(t *testing.T) { packages: tt.fields.packages, } - defer panicRecovery(t) + defer PanicRecovery(t) m.addImport(tt.args.path) if m.packages["foo"].importsAdded["bar"] != true && tt.expect { @@ -75,7 +75,7 @@ func Test_AddImport(t *testing.T) { } } -func Test_GetImports(t *testing.T) { +func TestGetImports(t *testing.T) { type fields struct { userAppPath string diffFile string @@ -131,7 +131,7 @@ func Test_GetImports(t *testing.T) { } } -func Test_CreateFunctionDeclaration(t *testing.T) { +func TestCreateFunctionDeclaration(t *testing.T) { type fields struct { userAppPath string diffFile string @@ -186,7 +186,7 @@ func Test_CreateFunctionDeclaration(t *testing.T) { currentPackage: tt.fields.currentPackage, packages: tt.fields.packages, } - defer panicRecovery(t) + defer PanicRecovery(t) m.createFunctionDeclaration(tt.args.decl) if tt.expect { @@ -207,7 +207,7 @@ func Test_CreateFunctionDeclaration(t *testing.T) { } } -func Test_UpdateFunctionDeclaration(t *testing.T) { +func TestUpdateFunctionDeclaration(t *testing.T) { type fields struct { userAppPath string diffFile string @@ -254,7 +254,7 @@ func Test_UpdateFunctionDeclaration(t *testing.T) { packages: tt.fields.packages, } - defer panicRecovery(t) + defer PanicRecovery(t) m.updateFunctionDeclaration(tt.args.decl) if tt.updates && reflect.DeepEqual(m.packages["foo"].tracedFuncs["bar"].body, tt.args.decl) == false { @@ -269,7 +269,7 @@ func Test_UpdateFunctionDeclaration(t *testing.T) { } // What if there are two instrumentable function invocations in a statement? -func Test_GetPackageFunctionInvocation(t *testing.T) { +func TestGetPackageFunctionInvocation(t *testing.T) { testFuncDecl := &dst.FuncDecl{} state := map[string]*packageState{"foo": { tracedFuncs: map[string]*tracedFunctionDecl{ @@ -426,14 +426,14 @@ func Test_GetPackageFunctionInvocation(t *testing.T) { currentPackage: tt.fields.currentPackage, packages: tt.fields.packages, } - defer panicRecovery(t) + defer PanicRecovery(t) got := m.findInvocationInfo(tt.args.node, tracestate.FunctionBody(codegen.DefaultTransactionVariable)) assert.Equal(t, tt.want, got) }) } } -func Test_ShouldInstrumentFunction(t *testing.T) { +func TestShouldInstrumentFunction(t *testing.T) { type fields struct { userAppPath string diffFile string @@ -498,7 +498,7 @@ func Test_ShouldInstrumentFunction(t *testing.T) { currentPackage: tt.fields.currentPackage, packages: tt.fields.packages, } - defer panicRecovery(t) + defer PanicRecovery(t) got := m.shouldInstrumentFunction(tt.args.inv) if got != tt.want { t.Errorf("InstrumentationManager.ShouldInstrumentFunction() = %v, want %v", got, tt.want) @@ -507,7 +507,7 @@ func Test_ShouldInstrumentFunction(t *testing.T) { } } -func Test_GetInvocationInfoFromCall(t *testing.T) { +func TestGetInvocationInfoFromCall(t *testing.T) { testFuncDecl := &dst.FuncDecl{} state := map[string]*packageState{"foo": { tracedFuncs: map[string]*tracedFunctionDecl{"bar": {body: testFuncDecl}}, @@ -568,14 +568,14 @@ func Test_GetInvocationInfoFromCall(t *testing.T) { currentPackage: tt.fields.currentPackage, packages: tt.fields.packages, } - defer panicRecovery(t) + defer PanicRecovery(t) got := m.getInvocationInfoFromCall(tt.args.call, tt.args.forTest) assert.Equal(t, tt.want, got) }) } } -func Test_NewInstrumentationManager(t *testing.T) { +func TestNewInstrumentationManager(t *testing.T) { type args struct { pkgs []*decorator.Package appName string @@ -646,7 +646,7 @@ func Test_NewInstrumentationManager(t *testing.T) { } } -func Test_SetPackage(t *testing.T) { +func TestSetPackage(t *testing.T) { type fields struct { currentPackage string } @@ -680,7 +680,7 @@ func Test_SetPackage(t *testing.T) { } } -func Test_GetPackageName(t *testing.T) { +func TestGetPackageName(t *testing.T) { type fields struct { currentPackage string } @@ -711,7 +711,7 @@ func Test_GetPackageName(t *testing.T) { } } -func Test_GetDecoratorPackage(t *testing.T) { +func TestGetDecoratorPackage(t *testing.T) { testPkg := &decorator.Package{Package: &packages.Package{ID: "test"}} type fields struct { currentPackage string @@ -751,7 +751,7 @@ func Test_GetDecoratorPackage(t *testing.T) { } } -func Test_IsDefinedInPackage(t *testing.T) { +func TestIsDefinedInPackage(t *testing.T) { type fields struct { packages map[string]*packageState } @@ -805,7 +805,7 @@ func Test_IsDefinedInPackage(t *testing.T) { } } -func Test_ResolvePath(t *testing.T) { +func TestResolvePath(t *testing.T) { type args struct { identPath string currentPackage string @@ -852,7 +852,7 @@ func Test_ResolvePath(t *testing.T) { } } -func Test_GetSortedPackages(t *testing.T) { +func TestGetSortedPackages(t *testing.T) { type fields struct { packages map[string]*packageState } @@ -900,7 +900,7 @@ func Test_GetSortedPackages(t *testing.T) { } } -func Test_LoadTracingFunctions(t *testing.T) { +func TestLoadTracingFunctions(t *testing.T) { mockStateless := func(m *InstrumentationManager, c *dstutil.Cursor) {} mockStateful := func(m *InstrumentationManager, stmt dst.Stmt, c *dstutil.Cursor, tracing *tracestate.State) bool { return false @@ -918,7 +918,7 @@ func Test_LoadTracingFunctions(t *testing.T) { { name: "loadStatelessTracingFunctions_adds_functions", testFunc: func(m *InstrumentationManager) { - m.loadStatelessTracingFunctions(mockStateless, mockStateless) + m.LoadStatelessTracingFunctions(mockStateless, mockStateless) }, verify: func(t *testing.T, m *InstrumentationManager) { assert.Equal(t, 2, len(m.tracingFunctions.stateless)) @@ -927,7 +927,7 @@ func Test_LoadTracingFunctions(t *testing.T) { { name: "loadStatefulTracingFunctions_adds_functions", testFunc: func(m *InstrumentationManager) { - m.loadStatefulTracingFunctions(mockStateful, mockStateful, mockStateful) + m.LoadStatefulTracingFunctions(mockStateful, mockStateful, mockStateful) }, verify: func(t *testing.T, m *InstrumentationManager) { assert.Equal(t, 3, len(m.tracingFunctions.stateful)) @@ -936,7 +936,7 @@ func Test_LoadTracingFunctions(t *testing.T) { { name: "loadDependencyScans_adds_scans", testFunc: func(m *InstrumentationManager) { - m.loadDependencyScans(mockDependency) + m.LoadDependencyScans(mockDependency) }, verify: func(t *testing.T, m *InstrumentationManager) { assert.Equal(t, 1, len(m.tracingFunctions.dependency)) @@ -945,7 +945,7 @@ func Test_LoadTracingFunctions(t *testing.T) { { name: "loadPreInstrumentationTracingFunctions_adds_functions", testFunc: func(m *InstrumentationManager) { - m.loadPreInstrumentationTracingFunctions(mockPreInstrumentation, mockPreInstrumentation) + m.LoadPreInstrumentationTracingFunctions(mockPreInstrumentation, mockPreInstrumentation) }, verify: func(t *testing.T, m *InstrumentationManager) { assert.Equal(t, 2, len(m.tracingFunctions.preinstrumentation)) @@ -961,7 +961,7 @@ func Test_LoadTracingFunctions(t *testing.T) { } } -func Test_ErrorNoMain(t *testing.T) { +func TestErrorNoMain(t *testing.T) { type args struct { path string } @@ -992,7 +992,7 @@ func Test_ErrorNoMain(t *testing.T) { } } -func Test_AddImport_EmptyPath(t *testing.T) { +func TestAddImport_EmptyPath(t *testing.T) { m := &InstrumentationManager{ packages: map[string]*packageState{"foo": {importsAdded: map[string]bool{}}}, currentPackage: "foo", @@ -1001,28 +1001,14 @@ func Test_AddImport_EmptyPath(t *testing.T) { assert.Equal(t, 0, len(m.packages["foo"].importsAdded)) } -func Test_DetectDependencyIntegrations(t *testing.T) { - tests := []struct { - name string - }{ - { - name: "loads_all_tracing_functions", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := NewInstrumentationManager([]*decorator.Package{}, "app", "agent", "diff.txt", "/path") - err := m.DetectDependencyIntegrations() - assert.NoError(t, err) - assert.Greater(t, len(m.tracingFunctions.stateless), 0) - assert.Greater(t, len(m.tracingFunctions.stateful), 0) - assert.Greater(t, len(m.tracingFunctions.dependency), 0) - assert.Greater(t, len(m.tracingFunctions.preinstrumentation), 0) - }) - } +// Test_DetectDependencyIntegrations is obsolete - integration registration moved to cmd/instrument.go +// Integration registration is now done via cmd/instrument.go's registerIntegrations() function +// which uses dependency injection to register all integration functions with the manager. +func TestDetectDependencyIntegrations(t *testing.T) { + t.Skip("Integration registration moved to cmd/instrument.go - test no longer applicable") } -func Test_InstrumentPackages(t *testing.T) { +func TestInstrumentPackages(t *testing.T) { type args struct { instrumentationFunctions []StatelessTracingFunction } @@ -1075,7 +1061,7 @@ func Test_InstrumentPackages(t *testing.T) { } } -func Test_ScanPackages(t *testing.T) { +func TestScanPackages(t *testing.T) { type args struct { instrumentationFunctions []PreInstrumentationTracingFunction } @@ -1128,7 +1114,7 @@ func Test_ScanPackages(t *testing.T) { } } -func Test_TracePackageCalls(t *testing.T) { +func TestTracePackageCalls(t *testing.T) { tests := []struct { name string manager *InstrumentationManager @@ -1137,7 +1123,7 @@ func Test_TracePackageCalls(t *testing.T) { { name: "errors_without_main_method", manager: &InstrumentationManager{ - packages: map[string]*packageState{}, + packages: map[string]*packageState{}, tracingFunctions: tracingFunctions{ dependency: []FactDiscoveryFunction{}, }, @@ -1155,7 +1141,7 @@ func Test_TracePackageCalls(t *testing.T) { } } -func Test_ScanApplication(t *testing.T) { +func TestScanApplication(t *testing.T) { tests := []struct { name string manager *InstrumentationManager @@ -1182,7 +1168,7 @@ func Test_ScanApplication(t *testing.T) { } } -func Test_InstrumentApplication(t *testing.T) { +func TestInstrumentApplication(t *testing.T) { tests := []struct { name string manager *InstrumentationManager diff --git a/parser/preinstrumentationtracing_test.go b/parser/preinstrumentationtracing_test.go index 4d6a4a91..ce248fad 100644 --- a/parser/preinstrumentationtracing_test.go +++ b/parser/preinstrumentationtracing_test.go @@ -133,22 +133,22 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - id, err := pseudo_uuid() + defer PanicRecovery(t) + id, err := Pseudo_uuid() if err != nil { t.Fatal(err) } testDir := fmt.Sprintf("tmp_%s", id) - defer cleanTestApp(t, testDir) + defer CleanTestApp(t, testDir) - manager := testInstrumentationManager(t, tt.code, testDir) + manager := TestInstrumentationManager(t, tt.code, testDir) pkg := manager.getDecoratorPackage() if pkg == nil { t.Fatalf("Package was nil: %+v", manager.packages) } - manager.loadPreInstrumentationTracingFunctions(DetectTransactions) + manager.LoadPreInstrumentationTracingFunctions(DetectTransactions) err = manager.ScanApplication() if err != nil { t.Fatalf("Failed to instrument packages: %v", err) @@ -222,22 +222,22 @@ func main() { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer panicRecovery(t) - id, err := pseudo_uuid() + defer PanicRecovery(t) + id, err := Pseudo_uuid() if err != nil { t.Fatal(err) } testDir := fmt.Sprintf("tmp_%s", id) - defer cleanTestApp(t, testDir) + defer CleanTestApp(t, testDir) - manager := testInstrumentationManager(t, tt.code, testDir) + manager := TestInstrumentationManager(t, tt.code, testDir) pkg := manager.getDecoratorPackage() if pkg == nil { t.Fatalf("Package was nil: %+v", manager.packages) } - manager.loadPreInstrumentationTracingFunctions(DetectTransactions, DetectErrors) + manager.LoadPreInstrumentationTracingFunctions(DetectTransactions, DetectErrors) err = manager.ScanApplication() if err != nil { t.Fatalf("Failed to instrument packages: %v", err) diff --git a/parser/testhelpers.go b/parser/testhelpers.go new file mode 100644 index 00000000..ed66d6d2 --- /dev/null +++ b/parser/testhelpers.go @@ -0,0 +1,198 @@ +// testhelpers.go provides shared test utilities for parser and integration tests. +// These functions are used across multiple test files and packages. +package parser + +import ( + "bytes" + "crypto/rand" + "fmt" + "os" + "path/filepath" + "runtime/debug" + "testing" + + "github.com/dave/dst" + "github.com/dave/dst/decorator" + "github.com/dave/dst/decorator/resolver/guess" + "github.com/dave/dst/dstutil" + "github.com/newrelic/go-easy-instrumentation/parser/tracestate" + "golang.org/x/tools/go/packages" +) + +// CreateTestApp creates a test app in the given directory with the given file name and contents. +// Codegen is expensive, so this will be skipped in short mode. +func CreateTestApp(t *testing.T, testAppDir, fileName, contents string) ([]*decorator.Package, error) { + if testing.Short() { + t.Skip("Skipping Stateful Tracing Function Integration Tests in short mode") + } + + err := os.Mkdir(testAppDir, 0755) + if err != nil { + return nil, err + } + + filepath := filepath.Join(testAppDir, fileName) + f, err := os.Create(filepath) + if err != nil { + return nil, err + } + + _, err = f.WriteString(contents) + if err != nil { + return nil, err + } + return decorator.Load(&packages.Config{Dir: testAppDir, Mode: packages.LoadSyntax}) +} + +// CleanTestApp removes the test app directory. +func CleanTestApp(t *testing.T, appDirectoryName string) { + err := os.RemoveAll(appDirectoryName) + if err != nil { + t.Logf("Failed to cleanup test app directory %s: %v", appDirectoryName, err) + } +} + +// PanicRecovery recovers from panics in tests and reports them with stack traces. +func PanicRecovery(t *testing.T) { + err := recover() + if err != nil { + t.Fatalf("%s recovered from panic: %+v\n\n%s", t.Name(), err, debug.Stack()) + } +} + +func Pseudo_uuid() (string, error) { + b := make([]byte, 16) + _, err := rand.Read(b) + if err != nil { + return "", fmt.Errorf("Failed to generate random number from bytes: %v", err) + } + return fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil +} + +func TestInstrumentationManager(t *testing.T, code, testAppDir string) *InstrumentationManager { + defer PanicRecovery(t) + fileName := "app.go" + pkgs, err := CreateTestApp(t, testAppDir, fileName, code) + if err != nil { + CleanTestApp(t, testAppDir) + t.Fatal(err) + } + + appName := "" + varName := "NewRelicAgent" + diffFile := filepath.Join(testAppDir, "new-relic-instrumentation.diff") + + manager := NewInstrumentationManager(pkgs, appName, varName, diffFile, testAppDir) + ConfigureTestInstrumentationManager(manager) + return manager +} + +func ConfigureTestInstrumentationManager(manager *InstrumentationManager) error { + pkgs := []string{} + for pkg := range manager.packages { + if pkg != "" { + pkgs = append(pkgs, pkg) + } + } + + if len(pkgs) == 0 { + return fmt.Errorf("no usable packages found in manager: %+v", manager.packages) + } + manager.setPackage(pkgs[0]) + return nil +} + +// RunStatefulTracingFunction runs a stateful tracing function against test code. +func RunStatefulTracingFunction(t *testing.T, code string, stmtFunc StatefulTracingFunction, downstream bool) string { + id, err := Pseudo_uuid() + if err != nil { + t.Fatal(err) + } + + testDir := fmt.Sprintf("tmp_%s", id) + defer CleanTestApp(t, testDir) + + manager := TestInstrumentationManager(t, code, testDir) + pkg := manager.getDecoratorPackage() + if pkg == nil { + t.Fatalf("Package was nil: %+v", manager.packages) + } + node := pkg.Syntax[0].Decls[1] + tracingState := tracestate.Main("app") + if downstream { + tracingState = tracestate.FunctionBody("txn") + } + + dstutil.Apply(node, nil, func(c *dstutil.Cursor) bool { + n := c.Node() + switch v := n.(type) { + case dst.Stmt: + stmtFunc(manager, v, c, tracingState) + } + return true + }) + restorer := decorator.NewRestorerWithImports(testDir, guess.New()) + + buf := bytes.NewBuffer([]byte{}) + err = restorer.Fprint(buf, pkg.Syntax[0]) + if err != nil { + t.Fatalf("Failed to restore the file: %v", err) + } + + return buf.String() +} + +// RunStatelessTracingFunction runs a stateless tracing function against test code. +func RunStatelessTracingFunction(t *testing.T, code string, tracingFunc StatelessTracingFunction, statefulTracingFuncs ...StatefulTracingFunction) string { + id, err := Pseudo_uuid() + if err != nil { + t.Fatal(err) + } + + testDir := fmt.Sprintf("tmp_%s", id) + defer CleanTestApp(t, testDir) + + manager := TestInstrumentationManager(t, code, testDir) + pkg := manager.getDecoratorPackage() + if pkg == nil { + t.Fatalf("Package was nil: %+v", manager.packages) + } + + manager.tracingFunctions.stateful = append(manager.tracingFunctions.stateful, statefulTracingFuncs...) + manager.tracingFunctions.stateless = append(manager.tracingFunctions.stateless, tracingFunc) + err = manager.TracePackageCalls() + if err != nil { + t.Fatalf("Failed to trace package calls: %v", err) + } + err = manager.InstrumentApplication() + if err != nil { + t.Fatalf("Failed to instrument packages: %v", err) + } + + restorer := decorator.NewRestorerWithImports(testDir, guess.New()) + buf := bytes.NewBuffer([]byte{}) + err = restorer.Fprint(buf, pkg.Syntax[0]) + if err != nil { + t.Fatalf("Failed to restore the file: %v", err) + } + + return buf.String() +} + +// UnitTest creates a temporary test package from the given code string. +func UnitTest(t *testing.T, code string) []*decorator.Package { + id, err := Pseudo_uuid() + if err != nil { + t.Fatal(err) + } + + testAppDir := fmt.Sprintf("tmp_%s", id) + fileName := "app.go" + pkgs, err := CreateTestApp(t, testAppDir, fileName, code) + defer CleanTestApp(t, testAppDir) + if err != nil { + t.Fatal(err) + } + + return pkgs +} diff --git a/parser/utils_test.go b/parser/utils_test.go index ca233549..9b5bbbc3 100644 --- a/parser/utils_test.go +++ b/parser/utils_test.go @@ -120,7 +120,7 @@ func unitTest(t *testing.T, code string) []*decorator.Package { return pkgs } -func testStatefulTracingFunction(t *testing.T, code string, stmtFunc StatefulTracingFunction, downstream bool) string { +func runStatefulTracingFunction(t *testing.T, code string, stmtFunc StatefulTracingFunction, downstream bool) string { id, err := pseudo_uuid() if err != nil { t.Fatal(err) @@ -130,7 +130,7 @@ func testStatefulTracingFunction(t *testing.T, code string, stmtFunc StatefulTra defer cleanTestApp(t, testDir) manager := testInstrumentationManager(t, code, testDir) - pkg := manager.getDecoratorPackage() + pkg := manager.GetDecoratorPackage() if pkg == nil { t.Fatalf("Package was nil: %+v", manager.packages) } @@ -159,7 +159,7 @@ func testStatefulTracingFunction(t *testing.T, code string, stmtFunc StatefulTra return buf.String() } -func testStatelessTracingFunction(t *testing.T, code string, tracingFunc StatelessTracingFunction, statefulTracingFuncs ...StatefulTracingFunction) string { +func runStatelessTracingFunction(t *testing.T, code string, tracingFunc StatelessTracingFunction, statefulTracingFuncs ...StatefulTracingFunction) string { id, err := pseudo_uuid() if err != nil { t.Fatal(err) @@ -194,3 +194,21 @@ func testStatelessTracingFunction(t *testing.T, code string, tracingFunc Statele return buf.String() } + +// UnitTest creates a temporary test package from the given code string. +func unitTestHelper(t *testing.T, code string) []*decorator.Package { + id, err := pseudo_uuid() + if err != nil { + t.Fatal(err) + } + + testAppDir := fmt.Sprintf("tmp_%s", id) + fileName := "app.go" + pkgs, err := createTestApp(t, testAppDir, fileName, code) + defer cleanTestApp(t, testAppDir) + if err != nil { + t.Fatal(err) + } + + return pkgs +} diff --git a/end-to-end-tests/README.md b/validation-tests/README.md similarity index 100% rename from end-to-end-tests/README.md rename to validation-tests/README.md diff --git a/end-to-end-tests/errors/expect.ref b/validation-tests/errors/expect.ref similarity index 100% rename from end-to-end-tests/errors/expect.ref rename to validation-tests/errors/expect.ref diff --git a/end-to-end-tests/errors/main.go b/validation-tests/errors/main.go similarity index 100% rename from end-to-end-tests/errors/main.go rename to validation-tests/errors/main.go diff --git a/end-to-end-tests/semi-instrumented/existing-app/expect.ref b/validation-tests/semi-instrumented/existing-app/expect.ref similarity index 100% rename from end-to-end-tests/semi-instrumented/existing-app/expect.ref rename to validation-tests/semi-instrumented/existing-app/expect.ref diff --git a/end-to-end-tests/semi-instrumented/existing-app/main.go b/validation-tests/semi-instrumented/existing-app/main.go similarity index 100% rename from end-to-end-tests/semi-instrumented/existing-app/main.go rename to validation-tests/semi-instrumented/existing-app/main.go diff --git a/end-to-end-tests/semi-instrumented/existing-errors/expect.ref b/validation-tests/semi-instrumented/existing-errors/expect.ref similarity index 100% rename from end-to-end-tests/semi-instrumented/existing-errors/expect.ref rename to validation-tests/semi-instrumented/existing-errors/expect.ref diff --git a/end-to-end-tests/semi-instrumented/existing-errors/main.go b/validation-tests/semi-instrumented/existing-errors/main.go similarity index 100% rename from end-to-end-tests/semi-instrumented/existing-errors/main.go rename to validation-tests/semi-instrumented/existing-errors/main.go diff --git a/end-to-end-tests/semi-instrumented/existing-transactions/expect.ref b/validation-tests/semi-instrumented/existing-transactions/expect.ref similarity index 100% rename from end-to-end-tests/semi-instrumented/existing-transactions/expect.ref rename to validation-tests/semi-instrumented/existing-transactions/expect.ref diff --git a/end-to-end-tests/semi-instrumented/existing-transactions/main.go b/validation-tests/semi-instrumented/existing-transactions/main.go similarity index 100% rename from end-to-end-tests/semi-instrumented/existing-transactions/main.go rename to validation-tests/semi-instrumented/existing-transactions/main.go diff --git a/end-to-end-tests/semi-instrumented/existing-web-app/expect.ref b/validation-tests/semi-instrumented/existing-web-app/expect.ref similarity index 100% rename from end-to-end-tests/semi-instrumented/existing-web-app/expect.ref rename to validation-tests/semi-instrumented/existing-web-app/expect.ref diff --git a/end-to-end-tests/semi-instrumented/existing-web-app/main.go b/validation-tests/semi-instrumented/existing-web-app/main.go similarity index 100% rename from end-to-end-tests/semi-instrumented/existing-web-app/main.go rename to validation-tests/semi-instrumented/existing-web-app/main.go diff --git a/validation-tests/testcases.json b/validation-tests/testcases.json new file mode 100644 index 00000000..e1d9b92e --- /dev/null +++ b/validation-tests/testcases.json @@ -0,0 +1,48 @@ +{ + "tests": [ + { + "name": "http web app", + "dir": "integrations/nrnethttp/example/http-app" + }, + { + "name": "http-mux web app", + "dir": "integrations/nrnethttp/example/http-mux-app" + }, + { + "name": "grpc app", + "dir": "integrations/nrgrpc/example/grpc", + "builds": [ + "integrations/nrgrpc/example/grpc/server", + "integrations/nrgrpc/example/grpc/client" + ] + }, + { + "name": "gin - basic", + "dir": "integrations/nrgin/example/basic" + }, + { + "name": "gin - multiple services", + "dir": "integrations/nrgin/example/multiple-service" + }, + { + "name": "semi-instrumented - existing transactions", + "dir": "validation-tests/semi-instrumented/existing-transactions" + }, + { + "name": "unit tests", + "dir": "validation-tests/unit-tests" + }, + { + "name": "gochi app", + "dir": "integrations/nrgochi/example/gochi" + }, + { + "name": "slog app", + "dir": "integrations/nrslog/example/slog-examples" + }, + { + "name": "mysql app", + "dir": "integrations/nrmysql/example/mysql" + } + ] +} diff --git a/end-to-end-tests/testrunner b/validation-tests/testrunner similarity index 98% rename from end-to-end-tests/testrunner rename to validation-tests/testrunner index f10af3ee..fa6c18d8 100755 --- a/end-to-end-tests/testrunner +++ b/validation-tests/testrunner @@ -6,7 +6,7 @@ import os import uuid DEFAULT_REF_FILE_NAME = "expect.ref" -TEST_CASE_FILE_NAME = "end-to-end-tests/testcases.json" +TEST_CASE_FILE_NAME = "validation-tests/testcases.json" cleanup = True dirname = os.path.basename(os.getcwd()) diff --git a/end-to-end-tests/unit-tests/expect.ref b/validation-tests/unit-tests/expect.ref similarity index 100% rename from end-to-end-tests/unit-tests/expect.ref rename to validation-tests/unit-tests/expect.ref diff --git a/end-to-end-tests/unit-tests/go.mod b/validation-tests/unit-tests/go.mod similarity index 100% rename from end-to-end-tests/unit-tests/go.mod rename to validation-tests/unit-tests/go.mod diff --git a/end-to-end-tests/unit-tests/go.sum b/validation-tests/unit-tests/go.sum similarity index 100% rename from end-to-end-tests/unit-tests/go.sum rename to validation-tests/unit-tests/go.sum diff --git a/end-to-end-tests/unit-tests/integration/integration_tests.go b/validation-tests/unit-tests/integration/integration_tests.go similarity index 100% rename from end-to-end-tests/unit-tests/integration/integration_tests.go rename to validation-tests/unit-tests/integration/integration_tests.go diff --git a/end-to-end-tests/unit-tests/main.go b/validation-tests/unit-tests/main.go similarity index 100% rename from end-to-end-tests/unit-tests/main.go rename to validation-tests/unit-tests/main.go diff --git a/end-to-end-tests/unit-tests/pkg1/pkg1.go b/validation-tests/unit-tests/pkg1/pkg1.go similarity index 100% rename from end-to-end-tests/unit-tests/pkg1/pkg1.go rename to validation-tests/unit-tests/pkg1/pkg1.go diff --git a/end-to-end-tests/unit-tests/pkg1/pkg1_test.go b/validation-tests/unit-tests/pkg1/pkg1_test.go similarity index 100% rename from end-to-end-tests/unit-tests/pkg1/pkg1_test.go rename to validation-tests/unit-tests/pkg1/pkg1_test.go diff --git a/end-to-end-tests/unit-tests/pkg2/pkg2.go b/validation-tests/unit-tests/pkg2/pkg2.go similarity index 100% rename from end-to-end-tests/unit-tests/pkg2/pkg2.go rename to validation-tests/unit-tests/pkg2/pkg2.go diff --git a/end-to-end-tests/unit-tests/pkg2/pkg2_test.go b/validation-tests/unit-tests/pkg2/pkg2_test.go similarity index 100% rename from end-to-end-tests/unit-tests/pkg2/pkg2_test.go rename to validation-tests/unit-tests/pkg2/pkg2_test.go diff --git a/end-to-end-tests/unit-tests/pkg3/pkg3.go b/validation-tests/unit-tests/pkg3/pkg3.go similarity index 100% rename from end-to-end-tests/unit-tests/pkg3/pkg3.go rename to validation-tests/unit-tests/pkg3/pkg3.go diff --git a/end-to-end-tests/unit-tests/pkg3/pkg3_test.go b/validation-tests/unit-tests/pkg3/pkg3_test.go similarity index 100% rename from end-to-end-tests/unit-tests/pkg3/pkg3_test.go rename to validation-tests/unit-tests/pkg3/pkg3_test.go diff --git a/end-to-end-tests/unit-tests/pkg4/obj.go b/validation-tests/unit-tests/pkg4/obj.go similarity index 100% rename from end-to-end-tests/unit-tests/pkg4/obj.go rename to validation-tests/unit-tests/pkg4/obj.go diff --git a/end-to-end-tests/unit-tests/util/util.go b/validation-tests/unit-tests/util/util.go similarity index 100% rename from end-to-end-tests/unit-tests/util/util.go rename to validation-tests/unit-tests/util/util.go diff --git a/end-to-end-tests/unit-tests/util/util_test.go b/validation-tests/unit-tests/util/util_test.go similarity index 100% rename from end-to-end-tests/unit-tests/util/util_test.go rename to validation-tests/unit-tests/util/util_test.go