From 7f6aacf8e991eff06428579a9516fa0e0a50f993 Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Mon, 30 Jun 2025 16:59:12 -0400 Subject: [PATCH 01/14] tests: Fixes integration_runner to not load opcache if already --- daemon/cmd/integration_runner/main.go | 11 ++---- .../internal/newrelic/integration/context.go | 2 ++ .../newrelic/integration/transaction.go | 34 ++++++++++++++++--- daemon/internal/newrelic/integration/util.go | 28 +++++++++++++++ 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/daemon/cmd/integration_runner/main.go b/daemon/cmd/integration_runner/main.go index 60c0247d8..a6354ff05 100644 --- a/daemon/cmd/integration_runner/main.go +++ b/daemon/cmd/integration_runner/main.go @@ -362,15 +362,8 @@ func main() { ctx.Settings["newrelic.loglevel"] = *flagLoglevel } - if false == *flagOpcacheOff { - // PHP Modules common to all tests - ctx.Settings["zend_extension"] = "opcache.so" - - // PHP INI values common to all tests - // These settings can be overwritten by adding new values to the INI block - ctx.Settings["opcache.enable"] = "1" - ctx.Settings["opcache.enable_cli"] = "1" - } + ctx.OPCacheModuleLoaded = integration.GetOPCacheModuleLoaded(*flagPHP, *flagCGI) + ctx.UseOPCache = !*flagOpcacheOff // If the user provided a custom agent extension, use it. if len(*flagAgent) > 0 { diff --git a/daemon/internal/newrelic/integration/context.go b/daemon/internal/newrelic/integration/context.go index 7801f31d2..fd39b79f6 100644 --- a/daemon/internal/newrelic/integration/context.go +++ b/daemon/internal/newrelic/integration/context.go @@ -18,6 +18,8 @@ type Context struct { Env map[string]string // environment variables to pass to each test Settings map[string]string // settings to pass to each test Timeout time.Duration // maximum test duration + OPCacheModuleLoaded map[string]bool // map of PHP and CGI to OPcache default loaded status + UseOPCache bool // whether to use OPcache in tests } func NewContext(php, cgi string) *Context { diff --git a/daemon/internal/newrelic/integration/transaction.go b/daemon/internal/newrelic/integration/transaction.go index b69b4b004..2b57f25a2 100644 --- a/daemon/internal/newrelic/integration/transaction.go +++ b/daemon/internal/newrelic/integration/transaction.go @@ -46,9 +46,22 @@ func PhpTx(src Script, env, settings map[string]string, ctx *Context) (Tx, error // Note: file path must be relative to the working directory. var txn Tx - args := phpArgs(nil, filepath.Base(src.Name()), false, settings) + // Make a copy of settings to avoid mutating the original map + phpSettings := make(map[string]string, len(settings)) + for k, v := range settings { + phpSettings[k] = v + } + if ctx.UseOPCache { + if !ctx.OPCacheModuleLoaded[ctx.PHP] { + phpSettings["zend_extension"] = "opcache.so" + } + phpSettings["opcache.enable"] = "1" + phpSettings["opcache.enable_cli"] = "1" + } + + args := phpArgs(nil, filepath.Base(src.Name()), false, phpSettings) - if ctx.Valgrind != "" && settings["newrelic.appname"] != "skipif" { + if ctx.Valgrind != "" && phpSettings["newrelic.appname"] != "skipif" { txn = &ValgrindCLI{ CLI: CLI{ Path: ctx.PHP, @@ -106,6 +119,19 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx return nil, fmt.Errorf("unable to create cgi request: %v", err) } + // Make a copy of settings to avoid mutating the original map + cgiSettings := make(map[string]string, len(settings)) + for k, v := range settings { + cgiSettings[k] = v + } + if ctx.UseOPCache { + if !ctx.OPCacheModuleLoaded[ctx.CGI] { + cgiSettings["zend_extension"] = "opcache.so" + } + cgiSettings["opcache.enable"] = "1" + cgiSettings["opcache.enable_cli"] = "1" + } + if ctx.Valgrind != "" { tx := &ValgrindCGI{ CGI: CGI{ @@ -113,7 +139,7 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx handler: &cgi.Handler{ Path: ctx.CGI, Dir: src.Dir(), - Args: phpArgs(nil, "", false, settings), + Args: phpArgs(nil, "", false, cgiSettings), }, }, Valgrind: ctx.Valgrind, @@ -144,7 +170,7 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx handler: &cgi.Handler{ Path: ctx.CGI, Dir: src.Dir(), - Args: phpArgs(nil, "", false, settings), + Args: phpArgs(nil, "", false, cgiSettings), }, } tx.handler.Env = append(tx.handler.Env, diff --git a/daemon/internal/newrelic/integration/util.go b/daemon/internal/newrelic/integration/util.go index 25ab9462f..2a96e6206 100644 --- a/daemon/internal/newrelic/integration/util.go +++ b/daemon/internal/newrelic/integration/util.go @@ -6,7 +6,9 @@ package integration import ( + "bytes" "fmt" + "os" "os/exec" ) @@ -31,3 +33,29 @@ func GetAgentVersion(agent_extension string) string { } return string(output) } + +func IsOPcacheLoaded(php_executable string) bool { + fmt.Printf("Checking if OPcache is loaded using %s\n", php_executable) + cmd := exec.Command(php_executable, "-m") + + output, err := cmd.Output() + + if err != nil { + fmt.Printf("Failed to check if OPcache is loaded: %v\n", err) + os.Exit(1) + } + + // Check if "Zend OPcache" is in the output + return bytes.Contains(output, []byte("Zend OPcache")) +} + +func GetOPCacheModuleLoaded(php, cgi string) map[string]bool { + result := make(map[string]bool) + + result[php] = IsOPcacheLoaded(php) + result[cgi] = IsOPcacheLoaded(cgi) + + fmt.Printf("OPcache loaded status: %+v\n", result) + + return result +} \ No newline at end of file From 73e64cd4d9f0b5ed67a028caa144bb9675472ca0 Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Tue, 1 Jul 2025 15:35:12 -0400 Subject: [PATCH 02/14] fix(testing): Handles opcache config better Previously it was assumed that the Zend OPCache module was NOT loaded in the environment which integration and mutliverse tests were run. Now the PHP Docker images have Zend OPCache loaded by default, causing errors when the integration_runner also tried to load this module. These changes identify if the module is loaded by default and handles setting the opcache.enable and opcache.enable_cli INI value based on the value of the "-opcache_off" command line option. --- daemon/internal/newrelic/integration/test.go | 23 +++++++- .../newrelic/integration/transaction.go | 52 +++++++++++++++++-- daemon/internal/newrelic/integration/util.go | 2 +- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/daemon/internal/newrelic/integration/test.go b/daemon/internal/newrelic/integration/test.go index 1911ac58c..d61fd531f 100644 --- a/daemon/internal/newrelic/integration/test.go +++ b/daemon/internal/newrelic/integration/test.go @@ -214,7 +214,28 @@ func (t *Test) MakeRun(ctx *Context) (Tx, error) { } } - settings = merge(settings, t.PhpModules) + // Make a copy of t.PhpModules and remove any entries containing "opcache.so" + // if opcache.so is loaded by default + // + // three cases: + // 1. C test - no opcache.so needed as PHP is not going to be run + // 2. Web test and php-cgi has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so + // 3. PHP test and php has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so + phpModulesCopy := make(map[string]string) + if (!t.IsC()) { + if (t.IsWeb() && ctx.OPCacheModuleLoaded[ctx.CGI]) || + (ctx.OPCacheModuleLoaded[ctx.PHP]) { + for k, v := range t.PhpModules { + if !strings.Contains(v, "opcache.so") { + phpModulesCopy[k] = v + } + } + } + } else { + fmt.Printf("ERROR - UNEXPECTED - Running C test: %s\n", t.Path) + os.Exit(1) + } + settings = merge(settings, phpModulesCopy) if t.IsC() { return CTx(ScriptFile(t.Path), t.Env, settings, headers, ctx) diff --git a/daemon/internal/newrelic/integration/transaction.go b/daemon/internal/newrelic/integration/transaction.go index 2b57f25a2..36a4ed594 100644 --- a/daemon/internal/newrelic/integration/transaction.go +++ b/daemon/internal/newrelic/integration/transaction.go @@ -48,15 +48,36 @@ func PhpTx(src Script, env, settings map[string]string, ctx *Context) (Tx, error // Make a copy of settings to avoid mutating the original map phpSettings := make(map[string]string, len(settings)) + setOPCacheEnable := true + setOPCacheEnableCLI := true for k, v := range settings { phpSettings[k] = v + + // see if settings affect opcache config + // if so then we will not set config below + if k == "opcache.enable" { + setOPCacheEnable = false + } else if k == "opcache.enable_cli" { + setOPCacheEnableCLI = false + } } if ctx.UseOPCache { - if !ctx.OPCacheModuleLoaded[ctx.PHP] { + if !ctx.OPCacheModuleLoaded[ctx.CGI] { phpSettings["zend_extension"] = "opcache.so" } - phpSettings["opcache.enable"] = "1" - phpSettings["opcache.enable_cli"] = "1" + if setOPCacheEnable { + phpSettings["opcache.enable"] = "1" + } + if setOPCacheEnableCLI { + phpSettings["opcache.enable_cli"] = "1" + } + } else { + if setOPCacheEnable { + phpSettings["opcache.enable"] = "0" + } + if setOPCacheEnableCLI { + phpSettings["opcache.enable_cli"] = "0" + } } args := phpArgs(nil, filepath.Base(src.Name()), false, phpSettings) @@ -121,15 +142,36 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx // Make a copy of settings to avoid mutating the original map cgiSettings := make(map[string]string, len(settings)) + setOPCacheEnable := true + setOPCacheEnableCLI := true for k, v := range settings { cgiSettings[k] = v + + // see if settings affect opcache config + // if so then we will not set config below + if k == "opcache.enable" { + setOPCacheEnable = false + } else if k == "opcache.enable_cli" { + setOPCacheEnableCLI = false + } } if ctx.UseOPCache { if !ctx.OPCacheModuleLoaded[ctx.CGI] { cgiSettings["zend_extension"] = "opcache.so" } - cgiSettings["opcache.enable"] = "1" - cgiSettings["opcache.enable_cli"] = "1" + if setOPCacheEnable { + cgiSettings["opcache.enable"] = "1" + } + if setOPCacheEnableCLI { + cgiSettings["opcache.enable_cli"] = "1" + } + } else { + if setOPCacheEnable { + cgiSettings["opcache.enable"] = "0" + } + if setOPCacheEnableCLI { + cgiSettings["opcache.enable_cli"] = "0" + } } if ctx.Valgrind != "" { diff --git a/daemon/internal/newrelic/integration/util.go b/daemon/internal/newrelic/integration/util.go index 2a96e6206..0fe8a7fbf 100644 --- a/daemon/internal/newrelic/integration/util.go +++ b/daemon/internal/newrelic/integration/util.go @@ -55,7 +55,7 @@ func GetOPCacheModuleLoaded(php, cgi string) map[string]bool { result[php] = IsOPcacheLoaded(php) result[cgi] = IsOPcacheLoaded(cgi) - fmt.Printf("OPcache loaded status: %+v\n", result) + fmt.Printf("OPcache default loading status: %+v\n", result) return result } \ No newline at end of file From cf48e0d3278baef694083504014b3855462c788e Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Tue, 1 Jul 2025 15:43:07 -0400 Subject: [PATCH 03/14] chore: Adds newline at end of file for gofmt --- daemon/internal/newrelic/integration/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/internal/newrelic/integration/util.go b/daemon/internal/newrelic/integration/util.go index 0fe8a7fbf..1f55e68e3 100644 --- a/daemon/internal/newrelic/integration/util.go +++ b/daemon/internal/newrelic/integration/util.go @@ -58,4 +58,4 @@ func GetOPCacheModuleLoaded(php, cgi string) map[string]bool { fmt.Printf("OPcache default loading status: %+v\n", result) return result -} \ No newline at end of file +} From c35e124b750daf3ca42a9e4bb178c41217ec88de Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Tue, 1 Jul 2025 15:57:04 -0400 Subject: [PATCH 04/14] chore: Tweaks message --- daemon/internal/newrelic/integration/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/internal/newrelic/integration/util.go b/daemon/internal/newrelic/integration/util.go index 1f55e68e3..dc6152ab4 100644 --- a/daemon/internal/newrelic/integration/util.go +++ b/daemon/internal/newrelic/integration/util.go @@ -55,7 +55,7 @@ func GetOPCacheModuleLoaded(php, cgi string) map[string]bool { result[php] = IsOPcacheLoaded(php) result[cgi] = IsOPcacheLoaded(cgi) - fmt.Printf("OPcache default loading status: %+v\n", result) + fmt.Printf("OPcache default loaded status: %+v\n", result) return result } From 6fe3be5246c413fae05333da19d91cff6f8fe9f7 Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Tue, 1 Jul 2025 16:14:23 -0400 Subject: [PATCH 05/14] chore(integration_runner): Fixes formatting --- daemon/internal/newrelic/integration/context.go | 16 ++++++++-------- daemon/internal/newrelic/integration/test.go | 4 ++-- .../internal/newrelic/integration/transaction.go | 4 ++-- daemon/internal/newrelic/integration/util.go | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/daemon/internal/newrelic/integration/context.go b/daemon/internal/newrelic/integration/context.go index fd39b79f6..6c5dd0224 100644 --- a/daemon/internal/newrelic/integration/context.go +++ b/daemon/internal/newrelic/integration/context.go @@ -12,14 +12,14 @@ import ( ) type Context struct { - PHP string // path to the PHP CLI executable - CGI string // path to the PHP CGI executable - Valgrind string // path to the Valgrind executable, or empty if disabled - Env map[string]string // environment variables to pass to each test - Settings map[string]string // settings to pass to each test - Timeout time.Duration // maximum test duration - OPCacheModuleLoaded map[string]bool // map of PHP and CGI to OPcache default loaded status - UseOPCache bool // whether to use OPcache in tests + PHP string // path to the PHP CLI executable + CGI string // path to the PHP CGI executable + Valgrind string // path to the Valgrind executable, or empty if disabled + Env map[string]string // environment variables to pass to each test + Settings map[string]string // settings to pass to each test + Timeout time.Duration // maximum test duration + OPCacheModuleLoaded map[string]bool // map of PHP and CGI to OPcache default loaded status + UseOPCache bool // whether to use OPcache in tests } func NewContext(php, cgi string) *Context { diff --git a/daemon/internal/newrelic/integration/test.go b/daemon/internal/newrelic/integration/test.go index d61fd531f..df65f7acb 100644 --- a/daemon/internal/newrelic/integration/test.go +++ b/daemon/internal/newrelic/integration/test.go @@ -216,13 +216,13 @@ func (t *Test) MakeRun(ctx *Context) (Tx, error) { // Make a copy of t.PhpModules and remove any entries containing "opcache.so" // if opcache.so is loaded by default - // + // // three cases: // 1. C test - no opcache.so needed as PHP is not going to be run // 2. Web test and php-cgi has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so // 3. PHP test and php has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so phpModulesCopy := make(map[string]string) - if (!t.IsC()) { + if !t.IsC() { if (t.IsWeb() && ctx.OPCacheModuleLoaded[ctx.CGI]) || (ctx.OPCacheModuleLoaded[ctx.PHP]) { for k, v := range t.PhpModules { diff --git a/daemon/internal/newrelic/integration/transaction.go b/daemon/internal/newrelic/integration/transaction.go index 36a4ed594..4636194be 100644 --- a/daemon/internal/newrelic/integration/transaction.go +++ b/daemon/internal/newrelic/integration/transaction.go @@ -77,7 +77,7 @@ func PhpTx(src Script, env, settings map[string]string, ctx *Context) (Tx, error } if setOPCacheEnableCLI { phpSettings["opcache.enable_cli"] = "0" - } + } } args := phpArgs(nil, filepath.Base(src.Name()), false, phpSettings) @@ -171,7 +171,7 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx } if setOPCacheEnableCLI { cgiSettings["opcache.enable_cli"] = "0" - } + } } if ctx.Valgrind != "" { diff --git a/daemon/internal/newrelic/integration/util.go b/daemon/internal/newrelic/integration/util.go index dc6152ab4..f64f68997 100644 --- a/daemon/internal/newrelic/integration/util.go +++ b/daemon/internal/newrelic/integration/util.go @@ -53,7 +53,7 @@ func GetOPCacheModuleLoaded(php, cgi string) map[string]bool { result := make(map[string]bool) result[php] = IsOPcacheLoaded(php) - result[cgi] = IsOPcacheLoaded(cgi) + result[cgi] = IsOPcacheLoaded(cgi) fmt.Printf("OPcache default loaded status: %+v\n", result) From 786a5f93e28526650361ad874c52c6538b02a593 Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Wed, 2 Jul 2025 15:28:08 -0400 Subject: [PATCH 06/14] chore(integration_runner): Refactors code The MakeRun() function serves as a common starting point for tests and it was brought up that the manipulatiuon of the settings which occurrd in PhpTx() and CgiTx() could be unified here to clean up the code. This code also asserts if a C test case is run as that is not a supported configuration. --- daemon/internal/newrelic/integration/test.go | 74 ++++++++++++++---- .../newrelic/integration/transaction.go | 76 +------------------ 2 files changed, 62 insertions(+), 88 deletions(-) diff --git a/daemon/internal/newrelic/integration/test.go b/daemon/internal/newrelic/integration/test.go index df65f7acb..c8214ac6a 100644 --- a/daemon/internal/newrelic/integration/test.go +++ b/daemon/internal/newrelic/integration/test.go @@ -202,6 +202,14 @@ func merge(a, b map[string]string) map[string]string { } func (t *Test) MakeRun(ctx *Context) (Tx, error) { + + // we don't support running C tests - assert this so we can + // troubleshoot if we try to run a C test + if t.IsC() { + fmt.Printf("ERROR - UNEXPECTED - Running C test: %s\n", t.Path) + os.Exit(1) + } + t.Env = merge(ctx.Env, t.Env) settings := merge(ctx.Settings, t.Settings) settings["newrelic.appname"] = t.Name @@ -214,6 +222,47 @@ func (t *Test) MakeRun(ctx *Context) (Tx, error) { } } + // Make a copy of settings to avoid mutating the original map + // Need to adjust settings to opcache + phpSettings := make(map[string]string, len(settings)) + setOPCacheEnable := true + setOPCacheEnableCLI := true + var php_executable string + if t.IsWeb() { + php_executable = ctx.CGI + } else { + php_executable = ctx.PHP + } + for k, v := range settings { + phpSettings[k] = v + + // see if settings affect opcache config + // if so then we will not set config below + if k == "opcache.enable" { + setOPCacheEnable = false + } else if k == "opcache.enable_cli" { + setOPCacheEnableCLI = false + } + } + if ctx.UseOPCache { + if !ctx.OPCacheModuleLoaded[php_executable] { + phpSettings["zend_extension"] = "opcache.so" + } + if setOPCacheEnable { + phpSettings["opcache.enable"] = "1" + } + if setOPCacheEnableCLI { + phpSettings["opcache.enable_cli"] = "1" + } + } else { + if setOPCacheEnable { + phpSettings["opcache.enable"] = "0" + } + if setOPCacheEnableCLI { + phpSettings["opcache.enable_cli"] = "0" + } + } + // Make a copy of t.PhpModules and remove any entries containing "opcache.so" // if opcache.so is loaded by default // @@ -222,28 +271,21 @@ func (t *Test) MakeRun(ctx *Context) (Tx, error) { // 2. Web test and php-cgi has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so // 3. PHP test and php has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so phpModulesCopy := make(map[string]string) - if !t.IsC() { - if (t.IsWeb() && ctx.OPCacheModuleLoaded[ctx.CGI]) || - (ctx.OPCacheModuleLoaded[ctx.PHP]) { - for k, v := range t.PhpModules { - if !strings.Contains(v, "opcache.so") { - phpModulesCopy[k] = v - } + if (t.IsWeb() && ctx.OPCacheModuleLoaded[ctx.CGI]) || + (ctx.OPCacheModuleLoaded[ctx.PHP]) { + for k, v := range t.PhpModules { + if !strings.Contains(v, "opcache.so") { + phpModulesCopy[k] = v } } - } else { - fmt.Printf("ERROR - UNEXPECTED - Running C test: %s\n", t.Path) - os.Exit(1) } - settings = merge(settings, phpModulesCopy) - if t.IsC() { - return CTx(ScriptFile(t.Path), t.Env, settings, headers, ctx) - } + phpSettings = merge(phpSettings, phpModulesCopy) + if t.IsWeb() { - return CgiTx(ScriptFile(t.Path), t.Env, settings, headers, ctx) + return CgiTx(ScriptFile(t.Path), t.Env, phpSettings, headers, ctx) } - return PhpTx(ScriptFile(t.Path), t.Env, settings, ctx) + return PhpTx(ScriptFile(t.Path), t.Env, phpSettings, ctx) } func (t *Test) MakeSkipIf(ctx *Context) (Tx, error) { diff --git a/daemon/internal/newrelic/integration/transaction.go b/daemon/internal/newrelic/integration/transaction.go index 4636194be..b69b4b004 100644 --- a/daemon/internal/newrelic/integration/transaction.go +++ b/daemon/internal/newrelic/integration/transaction.go @@ -46,43 +46,9 @@ func PhpTx(src Script, env, settings map[string]string, ctx *Context) (Tx, error // Note: file path must be relative to the working directory. var txn Tx - // Make a copy of settings to avoid mutating the original map - phpSettings := make(map[string]string, len(settings)) - setOPCacheEnable := true - setOPCacheEnableCLI := true - for k, v := range settings { - phpSettings[k] = v - - // see if settings affect opcache config - // if so then we will not set config below - if k == "opcache.enable" { - setOPCacheEnable = false - } else if k == "opcache.enable_cli" { - setOPCacheEnableCLI = false - } - } - if ctx.UseOPCache { - if !ctx.OPCacheModuleLoaded[ctx.CGI] { - phpSettings["zend_extension"] = "opcache.so" - } - if setOPCacheEnable { - phpSettings["opcache.enable"] = "1" - } - if setOPCacheEnableCLI { - phpSettings["opcache.enable_cli"] = "1" - } - } else { - if setOPCacheEnable { - phpSettings["opcache.enable"] = "0" - } - if setOPCacheEnableCLI { - phpSettings["opcache.enable_cli"] = "0" - } - } - - args := phpArgs(nil, filepath.Base(src.Name()), false, phpSettings) + args := phpArgs(nil, filepath.Base(src.Name()), false, settings) - if ctx.Valgrind != "" && phpSettings["newrelic.appname"] != "skipif" { + if ctx.Valgrind != "" && settings["newrelic.appname"] != "skipif" { txn = &ValgrindCLI{ CLI: CLI{ Path: ctx.PHP, @@ -140,40 +106,6 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx return nil, fmt.Errorf("unable to create cgi request: %v", err) } - // Make a copy of settings to avoid mutating the original map - cgiSettings := make(map[string]string, len(settings)) - setOPCacheEnable := true - setOPCacheEnableCLI := true - for k, v := range settings { - cgiSettings[k] = v - - // see if settings affect opcache config - // if so then we will not set config below - if k == "opcache.enable" { - setOPCacheEnable = false - } else if k == "opcache.enable_cli" { - setOPCacheEnableCLI = false - } - } - if ctx.UseOPCache { - if !ctx.OPCacheModuleLoaded[ctx.CGI] { - cgiSettings["zend_extension"] = "opcache.so" - } - if setOPCacheEnable { - cgiSettings["opcache.enable"] = "1" - } - if setOPCacheEnableCLI { - cgiSettings["opcache.enable_cli"] = "1" - } - } else { - if setOPCacheEnable { - cgiSettings["opcache.enable"] = "0" - } - if setOPCacheEnableCLI { - cgiSettings["opcache.enable_cli"] = "0" - } - } - if ctx.Valgrind != "" { tx := &ValgrindCGI{ CGI: CGI{ @@ -181,7 +113,7 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx handler: &cgi.Handler{ Path: ctx.CGI, Dir: src.Dir(), - Args: phpArgs(nil, "", false, cgiSettings), + Args: phpArgs(nil, "", false, settings), }, }, Valgrind: ctx.Valgrind, @@ -212,7 +144,7 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx handler: &cgi.Handler{ Path: ctx.CGI, Dir: src.Dir(), - Args: phpArgs(nil, "", false, cgiSettings), + Args: phpArgs(nil, "", false, settings), }, } tx.handler.Env = append(tx.handler.Env, From 2b140d0bb52b465f9f95cc957524d42de0a57dad Mon Sep 17 00:00:00 2001 From: Michael Fulbright <89205663+mfulb@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:52:06 -0400 Subject: [PATCH 07/14] Update daemon/internal/newrelic/integration/test.go Co-authored-by: Michal Nowacki --- daemon/internal/newrelic/integration/test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/daemon/internal/newrelic/integration/test.go b/daemon/internal/newrelic/integration/test.go index c8214ac6a..6f7aab3b9 100644 --- a/daemon/internal/newrelic/integration/test.go +++ b/daemon/internal/newrelic/integration/test.go @@ -266,10 +266,9 @@ func (t *Test) MakeRun(ctx *Context) (Tx, error) { // Make a copy of t.PhpModules and remove any entries containing "opcache.so" // if opcache.so is loaded by default // - // three cases: - // 1. C test - no opcache.so needed as PHP is not going to be run - // 2. Web test and php-cgi has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so - // 3. PHP test and php has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so + // two cases: + // 1. Web test and php-cgi has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so + // 2. PHP test and php has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so phpModulesCopy := make(map[string]string) if (t.IsWeb() && ctx.OPCacheModuleLoaded[ctx.CGI]) || (ctx.OPCacheModuleLoaded[ctx.PHP]) { From 87032c24d9e28540ddf07dd1329b0983aaf49803 Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Thu, 3 Jul 2025 12:53:44 -0400 Subject: [PATCH 08/14] fix(integratrion_runner): Uses knowledge of php executable to simplify code --- daemon/internal/newrelic/integration/test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daemon/internal/newrelic/integration/test.go b/daemon/internal/newrelic/integration/test.go index 6f7aab3b9..72f898860 100644 --- a/daemon/internal/newrelic/integration/test.go +++ b/daemon/internal/newrelic/integration/test.go @@ -270,8 +270,7 @@ func (t *Test) MakeRun(ctx *Context) (Tx, error) { // 1. Web test and php-cgi has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so // 2. PHP test and php has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so phpModulesCopy := make(map[string]string) - if (t.IsWeb() && ctx.OPCacheModuleLoaded[ctx.CGI]) || - (ctx.OPCacheModuleLoaded[ctx.PHP]) { + if ctx.OPCacheModuleLoaded[php_executable] { for k, v := range t.PhpModules { if !strings.Contains(v, "opcache.so") { phpModulesCopy[k] = v From c5527e8e191523d1b439bc3d8b3f7762a4a32682 Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Mon, 7 Jul 2025 15:29:52 -0400 Subject: [PATCH 09/14] feat(integration_runner): Adds debug flag to add output for troubleshooting --- daemon/cmd/integration_runner/main.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/daemon/cmd/integration_runner/main.go b/daemon/cmd/integration_runner/main.go index a6354ff05..04caaae68 100644 --- a/daemon/cmd/integration_runner/main.go +++ b/daemon/cmd/integration_runner/main.go @@ -51,6 +51,7 @@ var ( flagMaxCustomEvents = flag.Int("max_custom_events", 30000, "value for newrelic.custom_events.max_samples_stored") flagWarnIsFail = flag.Bool("warnisfail", false, "warn result is treated as a fail") flagOpcacheOff = flag.Bool("opcacheoff", false, "run without opcache. Some tests are intended to fail when run this way") + flagDebug = flag.Bool("debug", false, "enable debug logging for integration_runner") // externalPort is the port on which we start a server to handle // external calls. @@ -527,6 +528,10 @@ func runTest(t *integration.Test) { if skipIf != nil { _, body, err := skipIf.Execute() + if *flagDebug { + fmt.Printf("SkipIf output:\n%s\n", body) + } + if err != nil { t.Output = body t.Fatal(fmt.Errorf("error executing skipif: %v", err)) @@ -557,6 +562,10 @@ func runTest(t *integration.Test) { t.Duration = 0 } + if *flagDebug { + fmt.Printf("Test output:\n%s\n", body) + } + // Always save the test output. If an error occurred it may contain // critical information regarding the cause. Currently, it may also // contain valgrind commentary which we want to display. From f25f68583fdf0b534232bf9403c5d2d23ddd3f5f Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Mon, 7 Jul 2025 15:30:31 -0400 Subject: [PATCH 10/14] chore(integration_runner): Refactors code to handle PHPMODULES --- daemon/internal/newrelic/integration/test.go | 81 +++++++------------- 1 file changed, 29 insertions(+), 52 deletions(-) diff --git a/daemon/internal/newrelic/integration/test.go b/daemon/internal/newrelic/integration/test.go index 72f898860..0926564fd 100644 --- a/daemon/internal/newrelic/integration/test.go +++ b/daemon/internal/newrelic/integration/test.go @@ -201,6 +201,25 @@ func merge(a, b map[string]string) map[string]string { return merged } +// checks the context to see if opcache is loaded by default +// and then handles the PHP modules requests to make sure +// we don't load opcache.so if it is already loaded by default +func (t *Test) HandlePHPModules(php_executable string, ctx *Context) map[string]string { + // two cases: + // 1. Web test and php-cgi has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so + // 2. PHP test and php has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so + phpModulesCopy := make(map[string]string) + if ctx.OPCacheModuleLoaded[php_executable] { + for k, v := range t.PhpModules { + if !strings.Contains(v, "opcache.so") { + phpModulesCopy[k] = v + } + } + } + + return phpModulesCopy +} + func (t *Test) MakeRun(ctx *Context) (Tx, error) { // we don't support running C tests - assert this so we can @@ -222,68 +241,22 @@ func (t *Test) MakeRun(ctx *Context) (Tx, error) { } } - // Make a copy of settings to avoid mutating the original map - // Need to adjust settings to opcache - phpSettings := make(map[string]string, len(settings)) - setOPCacheEnable := true - setOPCacheEnableCLI := true var php_executable string if t.IsWeb() { php_executable = ctx.CGI - } else { + } else if t.IsPHP() { php_executable = ctx.PHP - } - for k, v := range settings { - phpSettings[k] = v - - // see if settings affect opcache config - // if so then we will not set config below - if k == "opcache.enable" { - setOPCacheEnable = false - } else if k == "opcache.enable_cli" { - setOPCacheEnableCLI = false - } - } - if ctx.UseOPCache { - if !ctx.OPCacheModuleLoaded[php_executable] { - phpSettings["zend_extension"] = "opcache.so" - } - if setOPCacheEnable { - phpSettings["opcache.enable"] = "1" - } - if setOPCacheEnableCLI { - phpSettings["opcache.enable_cli"] = "1" - } } else { - if setOPCacheEnable { - phpSettings["opcache.enable"] = "0" - } - if setOPCacheEnableCLI { - phpSettings["opcache.enable_cli"] = "0" - } - } - - // Make a copy of t.PhpModules and remove any entries containing "opcache.so" - // if opcache.so is loaded by default - // - // two cases: - // 1. Web test and php-cgi has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so - // 2. PHP test and php has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so - phpModulesCopy := make(map[string]string) - if ctx.OPCacheModuleLoaded[php_executable] { - for k, v := range t.PhpModules { - if !strings.Contains(v, "opcache.so") { - phpModulesCopy[k] = v - } - } + return nil, fmt.Errorf("unknown test type for %s", t.Path) } - phpSettings = merge(phpSettings, phpModulesCopy) + phpModulesCopy := t.HandlePHPModules(php_executable, ctx) + settings = merge(settings, phpModulesCopy) if t.IsWeb() { - return CgiTx(ScriptFile(t.Path), t.Env, phpSettings, headers, ctx) + return CgiTx(ScriptFile(t.Path), t.Env, settings, headers, ctx) } - return PhpTx(ScriptFile(t.Path), t.Env, phpSettings, ctx) + return PhpTx(ScriptFile(t.Path), t.Env, settings, ctx) } func (t *Test) MakeSkipIf(ctx *Context) (Tx, error) { @@ -302,6 +275,10 @@ func (t *Test) MakeSkipIf(ctx *Context) (Tx, error) { data: t.rawSkipIf, } + // handle the PHPMODULES directive + phpModulesCopy := t.HandlePHPModules(ctx.PHP, ctx) + settings = merge(settings, phpModulesCopy) + return PhpTx(src, t.Env, settings, ctx) } From 2380fd372b28a1c5502676697e0514d0536eddb6 Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Mon, 7 Jul 2025 15:30:52 -0400 Subject: [PATCH 11/14] chore(integration_runner): Refactors code to handle opcache settigns --- .../newrelic/integration/transaction.go | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/daemon/internal/newrelic/integration/transaction.go b/daemon/internal/newrelic/integration/transaction.go index b69b4b004..d006a649a 100644 --- a/daemon/internal/newrelic/integration/transaction.go +++ b/daemon/internal/newrelic/integration/transaction.go @@ -41,12 +41,61 @@ func flatten(x map[string]string) []string { return s } +// fixup settings to handle opcache module loading gracefully +// +// php_executable is the name to the PHP executable +// env is the environment variables to pass to the PHP process +// settings is the PHP settings to apply to the process +// ctx is the context containing configuration options +func fixupSettings(php_executable string, env, settings map[string]string, ctx *Context) map[string]string{ + + // Make a copy of settings to avoid mutating the original map + // Need to adjust settings to opcache + phpSettings := make(map[string]string, len(settings)) + setOPCacheEnable := true + setOPCacheEnableCLI := true + + for k, v := range settings { + phpSettings[k] = v + + // see if settings affect opcache config + // if so then we will not set config below + if k == "opcache.enable" { + setOPCacheEnable = false + } else if k == "opcache.enable_cli" { + setOPCacheEnableCLI = false + } + } + if ctx.UseOPCache { + if !ctx.OPCacheModuleLoaded[php_executable] { + phpSettings["zend_extension"] = "opcache.so" + } + if setOPCacheEnable { + phpSettings["opcache.enable"] = "1" + } + if setOPCacheEnableCLI { + phpSettings["opcache.enable_cli"] = "1" + } + } else { + if setOPCacheEnable { + phpSettings["opcache.enable"] = "0" + } + if setOPCacheEnableCLI { + phpSettings["opcache.enable_cli"] = "0" + } + } + + return phpSettings +} + // PhpTx constructs non-Web transactions to be executed by PHP. func PhpTx(src Script, env, settings map[string]string, ctx *Context) (Tx, error) { // Note: file path must be relative to the working directory. var txn Tx - args := phpArgs(nil, filepath.Base(src.Name()), false, settings) + newSettings := fixupSettings(ctx.PHP, env, settings, ctx) + + args := phpArgs(nil, filepath.Base(src.Name()), false, newSettings) if ctx.Valgrind != "" && settings["newrelic.appname"] != "skipif" { txn = &ValgrindCLI{ @@ -83,6 +132,8 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx var err error var txn Tx + newSettings := fixupSettings(ctx.CGI, env, settings, ctx) + req := &http.Request{ Method: env["REQUEST_METHOD"], RequestURI: "/" + filepath.Base(src.Name()) + "?" + env["QUERY_STRING"], @@ -113,7 +164,7 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx handler: &cgi.Handler{ Path: ctx.CGI, Dir: src.Dir(), - Args: phpArgs(nil, "", false, settings), + Args: phpArgs(nil, "", false, newSettings), }, }, Valgrind: ctx.Valgrind, @@ -144,7 +195,7 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx handler: &cgi.Handler{ Path: ctx.CGI, Dir: src.Dir(), - Args: phpArgs(nil, "", false, settings), + Args: phpArgs(nil, "", false, newSettings), }, } tx.handler.Env = append(tx.handler.Env, From fabe2561aa93c5fd244bab23dd63f06eb5a3540a Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Mon, 7 Jul 2025 15:32:41 -0400 Subject: [PATCH 12/14] fix(integration_runner): Adds options to not launch daemon --- daemon/internal/newrelic/integration/util.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/internal/newrelic/integration/util.go b/daemon/internal/newrelic/integration/util.go index f64f68997..daa75f607 100644 --- a/daemon/internal/newrelic/integration/util.go +++ b/daemon/internal/newrelic/integration/util.go @@ -13,7 +13,7 @@ import ( ) func GetPHPVersion() string { - cmd := exec.Command("php", "-r", "echo PHP_VERSION;") + cmd := exec.Command("php", "-d", "newrelic.daemon.dont_launch=3", "-r", "echo PHP_VERSION;") output, err := cmd.Output() if err != nil { @@ -25,7 +25,7 @@ func GetPHPVersion() string { } func GetAgentVersion(agent_extension string) string { - cmd := exec.Command("php", "-d", "extension="+agent_extension, "-r", "echo phpversion('newrelic');") + cmd := exec.Command("php", "-d", "newrelic.daemon.dont_launch=3", "-d", "extension="+agent_extension, "-r", "echo phpversion('newrelic');") output, err := cmd.Output() if err != nil { @@ -36,7 +36,7 @@ func GetAgentVersion(agent_extension string) string { func IsOPcacheLoaded(php_executable string) bool { fmt.Printf("Checking if OPcache is loaded using %s\n", php_executable) - cmd := exec.Command(php_executable, "-m") + cmd := exec.Command(php_executable, "-d", "newrelic.daemon.dont_launch=3", "-m") output, err := cmd.Output() From 2a89f4ba3ed735244036983c64c2cf2a2d3cd9b2 Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Mon, 7 Jul 2025 15:36:41 -0400 Subject: [PATCH 13/14] chore(integration_runner): Fixes formatting --- daemon/internal/newrelic/integration/transaction.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/internal/newrelic/integration/transaction.go b/daemon/internal/newrelic/integration/transaction.go index d006a649a..dc582c4f5 100644 --- a/daemon/internal/newrelic/integration/transaction.go +++ b/daemon/internal/newrelic/integration/transaction.go @@ -47,7 +47,7 @@ func flatten(x map[string]string) []string { // env is the environment variables to pass to the PHP process // settings is the PHP settings to apply to the process // ctx is the context containing configuration options -func fixupSettings(php_executable string, env, settings map[string]string, ctx *Context) map[string]string{ +func fixupSettings(php_executable string, env, settings map[string]string, ctx *Context) map[string]string { // Make a copy of settings to avoid mutating the original map // Need to adjust settings to opcache @@ -84,7 +84,7 @@ func fixupSettings(php_executable string, env, settings map[string]string, ctx * phpSettings["opcache.enable_cli"] = "0" } } - + return phpSettings } From 412fba7eae217aa11cb6d5cfac47ed4e2472df7a Mon Sep 17 00:00:00 2001 From: Michael Fulbright Date: Mon, 7 Jul 2025 16:21:06 -0400 Subject: [PATCH 14/14] fix(integration_runner): Handles "warn" from skipif --- daemon/cmd/integration_runner/main.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/daemon/cmd/integration_runner/main.go b/daemon/cmd/integration_runner/main.go index 04caaae68..04e32626b 100644 --- a/daemon/cmd/integration_runner/main.go +++ b/daemon/cmd/integration_runner/main.go @@ -453,8 +453,14 @@ func main() { if numFailed > 0 { os.Exit(1) } - if *flagWarnIsFail && numWarned > 0 { - os.Exit(2) + + if numWarned > 0 { + if *flagWarnIsFail { + fmt.Println("WARNING: some tests were warned, but are treated as failures because --warnisfail is true") + os.Exit(2) + } else { + fmt.Printf("WARNING: some tests were warned, but are not treated as failures because --warnisfail is false\n") + } } } @@ -543,6 +549,12 @@ func runTest(t *integration.Test) { t.Skip(reason) return } + + if warnRE.Match(body) && *flagWarnIsFail { + reason := string(bytes.TrimSpace(head(body))) + t.Warn(reason) + return + } } // Reset global response headers before the test is run. This feature