Skip to content

Commit 4e52c16

Browse files
mfulblavarou
authored andcommitted
tests: fix integration_runner opcache module loading (#1097)
With recent official PHP docker images the Zend OPCache extension is loaded by default. This is causing some test failures as PHP will output a warning message when the `integration_runner` attempts to load this extension. As a result any `EXPECT` statements in tests will fail due to this extra output. The solution chosen was to use the “-m” option for the `php` and `php-cgi` binaries to probe if the Zend OPCache module is loaded by default in the PHP environment. Both binaries are used in testing so the defaults of both are considered. The value of the integration_runner command line option `opcache_off` is also noted. When a test is going to be run this information is used to determine if the `zend_extension=opcache.so` is needed as well as setting the values of `opcache.enable` and `opcache.enable_cli`. These INI values needed special handling because it is possible these are overwritten by values in the INI stanza of a given test case. Also consideration is needed to if the `PHPMODULES` stanza exists in the test case and specifies loading the `opcache.so` module. --------- Co-authored-by: Michal Nowacki <[email protected]>
1 parent 9096658 commit 4e52c16

File tree

5 files changed

+159
-26
lines changed

5 files changed

+159
-26
lines changed

daemon/cmd/integration_runner/main.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ var (
5151
flagMaxCustomEvents = flag.Int("max_custom_events", 30000, "value for newrelic.custom_events.max_samples_stored")
5252
flagWarnIsFail = flag.Bool("warnisfail", false, "warn result is treated as a fail")
5353
flagOpcacheOff = flag.Bool("opcacheoff", false, "run without opcache. Some tests are intended to fail when run this way")
54+
flagDebug = flag.Bool("debug", false, "enable debug logging for integration_runner")
5455

5556
// externalPort is the port on which we start a server to handle
5657
// external calls.
@@ -362,15 +363,8 @@ func main() {
362363
ctx.Settings["newrelic.loglevel"] = *flagLoglevel
363364
}
364365

365-
if false == *flagOpcacheOff {
366-
// PHP Modules common to all tests
367-
ctx.Settings["zend_extension"] = "opcache.so"
368-
369-
// PHP INI values common to all tests
370-
// These settings can be overwritten by adding new values to the INI block
371-
ctx.Settings["opcache.enable"] = "1"
372-
ctx.Settings["opcache.enable_cli"] = "1"
373-
}
366+
ctx.OPCacheModuleLoaded = integration.GetOPCacheModuleLoaded(*flagPHP, *flagCGI)
367+
ctx.UseOPCache = !*flagOpcacheOff
374368

375369
// If the user provided a custom agent extension, use it.
376370
if len(*flagAgent) > 0 {
@@ -459,8 +453,14 @@ func main() {
459453
if numFailed > 0 {
460454
os.Exit(1)
461455
}
462-
if *flagWarnIsFail && numWarned > 0 {
463-
os.Exit(2)
456+
457+
if numWarned > 0 {
458+
if *flagWarnIsFail {
459+
fmt.Println("WARNING: some tests were warned, but are treated as failures because --warnisfail is true")
460+
os.Exit(2)
461+
} else {
462+
fmt.Printf("WARNING: some tests were warned, but are not treated as failures because --warnisfail is false\n")
463+
}
464464
}
465465
}
466466

@@ -534,6 +534,10 @@ func runTest(t *integration.Test) {
534534
if skipIf != nil {
535535
_, body, err := skipIf.Execute()
536536

537+
if *flagDebug {
538+
fmt.Printf("SkipIf output:\n%s\n", body)
539+
}
540+
537541
if err != nil {
538542
t.Output = body
539543
t.Fatal(fmt.Errorf("error executing skipif: %v", err))
@@ -545,6 +549,12 @@ func runTest(t *integration.Test) {
545549
t.Skip(reason)
546550
return
547551
}
552+
553+
if warnRE.Match(body) && *flagWarnIsFail {
554+
reason := string(bytes.TrimSpace(head(body)))
555+
t.Warn(reason)
556+
return
557+
}
548558
}
549559

550560
// Reset global response headers before the test is run. This feature
@@ -564,6 +574,10 @@ func runTest(t *integration.Test) {
564574
t.Duration = 0
565575
}
566576

577+
if *flagDebug {
578+
fmt.Printf("Test output:\n%s\n", body)
579+
}
580+
567581
// Always save the test output. If an error occurred it may contain
568582
// critical information regarding the cause. Currently, it may also
569583
// contain valgrind commentary which we want to display.

daemon/internal/newrelic/integration/context.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import (
1212
)
1313

1414
type Context struct {
15-
PHP string // path to the PHP CLI executable
16-
CGI string // path to the PHP CGI executable
17-
Valgrind string // path to the Valgrind executable, or empty if disabled
18-
Env map[string]string // environment variables to pass to each test
19-
Settings map[string]string // settings to pass to each test
20-
Timeout time.Duration // maximum test duration
15+
PHP string // path to the PHP CLI executable
16+
CGI string // path to the PHP CGI executable
17+
Valgrind string // path to the Valgrind executable, or empty if disabled
18+
Env map[string]string // environment variables to pass to each test
19+
Settings map[string]string // settings to pass to each test
20+
Timeout time.Duration // maximum test duration
21+
OPCacheModuleLoaded map[string]bool // map of PHP and CGI to OPcache default loaded status
22+
UseOPCache bool // whether to use OPcache in tests
2123
}
2224

2325
func NewContext(php, cgi string) *Context {

daemon/internal/newrelic/integration/test.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,34 @@ func merge(a, b map[string]string) map[string]string {
201201
return merged
202202
}
203203

204+
// checks the context to see if opcache is loaded by default
205+
// and then handles the PHP modules requests to make sure
206+
// we don't load opcache.so if it is already loaded by default
207+
func (t *Test) HandlePHPModules(php_executable string, ctx *Context) map[string]string {
208+
// two cases:
209+
// 1. Web test and php-cgi has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so
210+
// 2. PHP test and php has opcache.so loaded by default - remove any PHPMODULE spec for opcache.so
211+
phpModulesCopy := make(map[string]string)
212+
if ctx.OPCacheModuleLoaded[php_executable] {
213+
for k, v := range t.PhpModules {
214+
if !strings.Contains(v, "opcache.so") {
215+
phpModulesCopy[k] = v
216+
}
217+
}
218+
}
219+
220+
return phpModulesCopy
221+
}
222+
204223
func (t *Test) MakeRun(ctx *Context) (Tx, error) {
224+
225+
// we don't support running C tests - assert this so we can
226+
// troubleshoot if we try to run a C test
227+
if t.IsC() {
228+
fmt.Printf("ERROR - UNEXPECTED - Running C test: %s\n", t.Path)
229+
os.Exit(1)
230+
}
231+
205232
t.Env = merge(ctx.Env, t.Env)
206233
settings := merge(ctx.Settings, t.Settings)
207234
settings["newrelic.appname"] = t.Name
@@ -214,11 +241,18 @@ func (t *Test) MakeRun(ctx *Context) (Tx, error) {
214241
}
215242
}
216243

217-
settings = merge(settings, t.PhpModules)
218-
219-
if t.IsC() {
220-
return CTx(ScriptFile(t.Path), t.Env, settings, headers, ctx)
244+
var php_executable string
245+
if t.IsWeb() {
246+
php_executable = ctx.CGI
247+
} else if t.IsPHP() {
248+
php_executable = ctx.PHP
249+
} else {
250+
return nil, fmt.Errorf("unknown test type for %s", t.Path)
221251
}
252+
253+
phpModulesCopy := t.HandlePHPModules(php_executable, ctx)
254+
settings = merge(settings, phpModulesCopy)
255+
222256
if t.IsWeb() {
223257
return CgiTx(ScriptFile(t.Path), t.Env, settings, headers, ctx)
224258
}
@@ -241,6 +275,10 @@ func (t *Test) MakeSkipIf(ctx *Context) (Tx, error) {
241275
data: t.rawSkipIf,
242276
}
243277

278+
// handle the PHPMODULES directive
279+
phpModulesCopy := t.HandlePHPModules(ctx.PHP, ctx)
280+
settings = merge(settings, phpModulesCopy)
281+
244282
return PhpTx(src, t.Env, settings, ctx)
245283
}
246284

daemon/internal/newrelic/integration/transaction.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,61 @@ func flatten(x map[string]string) []string {
4141
return s
4242
}
4343

44+
// fixup settings to handle opcache module loading gracefully
45+
//
46+
// php_executable is the name to the PHP executable
47+
// env is the environment variables to pass to the PHP process
48+
// settings is the PHP settings to apply to the process
49+
// ctx is the context containing configuration options
50+
func fixupSettings(php_executable string, env, settings map[string]string, ctx *Context) map[string]string {
51+
52+
// Make a copy of settings to avoid mutating the original map
53+
// Need to adjust settings to opcache
54+
phpSettings := make(map[string]string, len(settings))
55+
setOPCacheEnable := true
56+
setOPCacheEnableCLI := true
57+
58+
for k, v := range settings {
59+
phpSettings[k] = v
60+
61+
// see if settings affect opcache config
62+
// if so then we will not set config below
63+
if k == "opcache.enable" {
64+
setOPCacheEnable = false
65+
} else if k == "opcache.enable_cli" {
66+
setOPCacheEnableCLI = false
67+
}
68+
}
69+
if ctx.UseOPCache {
70+
if !ctx.OPCacheModuleLoaded[php_executable] {
71+
phpSettings["zend_extension"] = "opcache.so"
72+
}
73+
if setOPCacheEnable {
74+
phpSettings["opcache.enable"] = "1"
75+
}
76+
if setOPCacheEnableCLI {
77+
phpSettings["opcache.enable_cli"] = "1"
78+
}
79+
} else {
80+
if setOPCacheEnable {
81+
phpSettings["opcache.enable"] = "0"
82+
}
83+
if setOPCacheEnableCLI {
84+
phpSettings["opcache.enable_cli"] = "0"
85+
}
86+
}
87+
88+
return phpSettings
89+
}
90+
4491
// PhpTx constructs non-Web transactions to be executed by PHP.
4592
func PhpTx(src Script, env, settings map[string]string, ctx *Context) (Tx, error) {
4693
// Note: file path must be relative to the working directory.
4794
var txn Tx
4895

49-
args := phpArgs(nil, filepath.Base(src.Name()), false, settings)
96+
newSettings := fixupSettings(ctx.PHP, env, settings, ctx)
97+
98+
args := phpArgs(nil, filepath.Base(src.Name()), false, newSettings)
5099

51100
if ctx.Valgrind != "" && settings["newrelic.appname"] != "skipif" {
52101
txn = &ValgrindCLI{
@@ -83,6 +132,8 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx
83132
var err error
84133
var txn Tx
85134

135+
newSettings := fixupSettings(ctx.CGI, env, settings, ctx)
136+
86137
req := &http.Request{
87138
Method: env["REQUEST_METHOD"],
88139
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
113164
handler: &cgi.Handler{
114165
Path: ctx.CGI,
115166
Dir: src.Dir(),
116-
Args: phpArgs(nil, "", false, settings),
167+
Args: phpArgs(nil, "", false, newSettings),
117168
},
118169
},
119170
Valgrind: ctx.Valgrind,
@@ -144,7 +195,7 @@ func CgiTx(src Script, env, settings map[string]string, headers http.Header, ctx
144195
handler: &cgi.Handler{
145196
Path: ctx.CGI,
146197
Dir: src.Dir(),
147-
Args: phpArgs(nil, "", false, settings),
198+
Args: phpArgs(nil, "", false, newSettings),
148199
},
149200
}
150201
tx.handler.Env = append(tx.handler.Env,

daemon/internal/newrelic/integration/util.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
package integration
77

88
import (
9+
"bytes"
910
"fmt"
11+
"os"
1012
"os/exec"
1113
)
1214

1315
func GetPHPVersion() string {
14-
cmd := exec.Command("php", "-r", "echo PHP_VERSION;")
16+
cmd := exec.Command("php", "-d", "newrelic.daemon.dont_launch=3", "-r", "echo PHP_VERSION;")
1517

1618
output, err := cmd.Output()
1719
if err != nil {
@@ -23,11 +25,37 @@ func GetPHPVersion() string {
2325
}
2426

2527
func GetAgentVersion(agent_extension string) string {
26-
cmd := exec.Command("php", "-d", "extension="+agent_extension, "-r", "echo phpversion('newrelic');")
28+
cmd := exec.Command("php", "-d", "newrelic.daemon.dont_launch=3", "-d", "extension="+agent_extension, "-r", "echo phpversion('newrelic');")
2729

2830
output, err := cmd.Output()
2931
if err != nil {
3032
return fmt.Errorf("Failed to get agent version: %v", err).Error()
3133
}
3234
return string(output)
3335
}
36+
37+
func IsOPcacheLoaded(php_executable string) bool {
38+
fmt.Printf("Checking if OPcache is loaded using %s\n", php_executable)
39+
cmd := exec.Command(php_executable, "-d", "newrelic.daemon.dont_launch=3", "-m")
40+
41+
output, err := cmd.Output()
42+
43+
if err != nil {
44+
fmt.Printf("Failed to check if OPcache is loaded: %v\n", err)
45+
os.Exit(1)
46+
}
47+
48+
// Check if "Zend OPcache" is in the output
49+
return bytes.Contains(output, []byte("Zend OPcache"))
50+
}
51+
52+
func GetOPCacheModuleLoaded(php, cgi string) map[string]bool {
53+
result := make(map[string]bool)
54+
55+
result[php] = IsOPcacheLoaded(php)
56+
result[cgi] = IsOPcacheLoaded(cgi)
57+
58+
fmt.Printf("OPcache default loaded status: %+v\n", result)
59+
60+
return result
61+
}

0 commit comments

Comments
 (0)