diff --git a/src/newrelic/integration/parse.go b/src/newrelic/integration/parse.go index 554972556..36745dfa7 100644 --- a/src/newrelic/integration/parse.go +++ b/src/newrelic/integration/parse.go @@ -23,6 +23,7 @@ var ( "HEADERS": parseHeaders, "SKIPIF": parseRawSkipIf, "INI": parseSettings, + "PHPMODULES": parsePHPModules, "CONFIG": parseConfig, "DESCRIPTION": parseDescription, "EXPECT_ANALYTICS_EVENTS": parseAnalyticEvents, @@ -196,6 +197,35 @@ func parseSettings(t *Test, content []byte) error { return nil } +func parsePHPModules(t *Test, content []byte) error { + trimmed := bytes.TrimSpace(content) + settings := make(map[string]string) + scanner := bufio.NewScanner(bytes.NewReader(trimmed)) + delim := []byte("=") + + for scanner.Scan() { + parts := bytes.SplitN(scanner.Bytes(), delim, 2) + switch len(parts) { + case 2: + key, value := bytes.TrimSpace(parts[0]), bytes.TrimSpace(parts[1]) + if len(key) > 0 { + settings[string(key)] = string(value) + } else { + return errBadSetting + } + case 1: + return errBadSetting + } + } + + if err := scanner.Err(); err != nil { + return err + } + t.PhpModules = settings + return nil +} + + func parseAnalyticEvents(test *Test, content []byte) error { test.analyticEvents = content return nil diff --git a/src/newrelic/integration/test.go b/src/newrelic/integration/test.go index 4ce126a33..f5dfd9b8f 100644 --- a/src/newrelic/integration/test.go +++ b/src/newrelic/integration/test.go @@ -51,10 +51,11 @@ type Test struct { // Raw parsed test information used to construct the Tx. // The settings and env do not include global env and // global settings. - rawSkipIf []byte - Env map[string]string - Settings map[string]string - headers http.Header + rawSkipIf []byte + Env map[string]string + Settings map[string]string + PhpModules map[string]string + headers http.Header // When non-empty describes why failed should be true after the test // is run. This field may be set in the test definition to indicate @@ -195,6 +196,8 @@ func (t *Test) MakeRun(ctx *Context) (Tx, error) { } } + settings = merge(settings, t.PhpModules) + if t.IsC() { return CTx(ScriptFile(t.Path), env, settings, headers, ctx) } diff --git a/tests/integration/jit/function/skipif.inc b/tests/integration/jit/function/skipif.inc new file mode 100644 index 000000000..ac88b510a --- /dev/null +++ b/tests/integration/jit/function/skipif.inc @@ -0,0 +1,15 @@ +functionName(); + } +} + +for($i = 0; $i < 5; ++$i){ + new LittleClass; +} +echo "OK\n"; diff --git a/tests/integration/jit/function/test_span_events_are_created_from_segments.php b/tests/integration/jit/function/test_span_events_are_created_from_segments.php new file mode 100644 index 000000000..b543794bd --- /dev/null +++ b/tests/integration/jit/function/test_span_events_are_created_from_segments.php @@ -0,0 +1,118 @@ + 'FakeDB', +)); diff --git a/tests/integration/jit/function/test_span_events_are_created_upon_caught_error.php b/tests/integration/jit/function/test_span_events_are_created_upon_caught_error.php new file mode 100644 index 000000000..bca70be7e --- /dev/null +++ b/tests/integration/jit/function/test_span_events_are_created_upon_caught_error.php @@ -0,0 +1,194 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/function/test_span_events_are_created_upon_exit.php b/tests/integration/jit/function/test_span_events_are_created_upon_exit.php new file mode 100644 index 000000000..3274bf154 --- /dev/null +++ b/tests/integration/jit/function/test_span_events_are_created_upon_exit.php @@ -0,0 +1,123 @@ + 'FakeDB', + ) +); +a(); diff --git a/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_error.php b/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_error.php new file mode 100644 index 000000000..c3f064b6d --- /dev/null +++ b/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_error.php @@ -0,0 +1,133 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_handled_exception.php b/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_handled_exception.php new file mode 100644 index 000000000..2381e2045 --- /dev/null +++ b/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_handled_exception.php @@ -0,0 +1,196 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_handled_exception_invalid_handler.php b/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_handled_exception_invalid_handler.php new file mode 100644 index 000000000..fd00e971c --- /dev/null +++ b/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_handled_exception_invalid_handler.php @@ -0,0 +1,210 @@ +=")) { + die("skip: PHP > 8.1 not supported\n"); +} + +require('skipif.inc'); + + +*/ + +/*INI +newrelic.distributed_tracing_enabled=1 +newrelic.transaction_tracer.threshold = 0 +newrelic.span_events_enabled=1 +newrelic.cross_application_tracer.enabled = false +display_errors=1 +log_errors=0 +error_reporting = E_ALL +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=32M +opcache.jit=function +*/ + + +/*PHPMODULES +zend_extension=opcache.so +*/ + +/*EXPECT_ERROR_EVENTS +[ + "?? agent run id", + { + "reservoir_size": "??", + "events_seen": 1 + }, + [ + [ + { + "type": "TransactionError", + "timestamp": "??", + "error.class": "RuntimeException", + "error.message": "Uncaught exception 'RuntimeException' with message 'oops' in __FILE__:??", + "transactionName": "OtherTransaction\/php__FILE__", + "duration": "??", + "databaseDuration": "??", + "databaseCallCount": "??", + "nr.transactionGuid": "??", + "guid": "??", + "sampled": true, + "priority": "??", + "traceId": "??", + "spanId": "??" + }, + {}, + {} + ] + ] +] +*/ + + +/*EXPECT_SPAN_EVENTS +[ + "?? agent run id", + { + "reservoir_size": 10000, + "events_seen": 4 + }, + [ + [ + { + "traceId": "??", + "duration": "??", + "transactionId": "??", + "name": "OtherTransaction\/php__FILE__", + "guid": "??", + "type": "Span", + "category": "generic", + "priority": "??", + "sampled": true, + "nr.entryPoint": true, + "timestamp": "??", + "transaction.name": "OtherTransaction\/php__FILE__" + }, + {}, + {} + ], + [ + { + "type": "Span", + "traceId": "??", + "transactionId": "??", + "sampled": true, + "priority": "??", + "name": "Datastore\/statement\/FakeDB\/other\/other", + "guid": "??", + "timestamp": "??", + "duration": "??", + "category": "datastore", + "parentId": "??", + "span.kind": "client", + "component": "FakeDB" + }, + {}, + { + "db.instance": "unknown", + "peer.hostname": "unknown", + "peer.address": "unknown:unknown" + } + ], + [ + { + "type": "Span", + "traceId": "??", + "transactionId": "??", + "sampled": true, + "priority": "??", + "name": "Custom\/a", + "guid": "??", + "timestamp": "??", + "duration": "??", + "category": "generic", + "parentId": "??" + }, + {}, + { + "error.message": "Uncaught exception 'RuntimeException' with message 'oops' in __FILE__:??", + "error.class": "RuntimeException", + "code.lineno": "??", + "code.filepath": "__FILE__", + "code.function": "a" + } + ], + [ + { + "type": "Span", + "traceId": "??", + "transactionId": "??", + "sampled": true, + "priority": "??", + "name": "Custom\/{closure}", + "guid": "??", + "timestamp": "??", + "duration": "??", + "category": "generic", + "parentId": "??" + }, + {}, + { + "code.lineno": "??", + "code.filepath": "__FILE__", + "code.function": "{closure}" + } + ] + ] +] +*/ + +/*EXPECT +*/ + +set_exception_handler( + function () { + time_nanosleep(0, 100000000); + exit(0); + } +); + +function a() +{ + time_nanosleep(0, 100000000); + throw new RuntimeException('oops'); +} + +newrelic_record_datastore_segment( + function () { + time_nanosleep(0, 100000000); + }, array( + 'product' => 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_unhandled_exception.php b/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_unhandled_exception.php new file mode 100644 index 000000000..f619cab45 --- /dev/null +++ b/tests/integration/jit/function/test_span_events_are_created_upon_uncaught_unhandled_exception.php @@ -0,0 +1,170 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/function/test_span_events_error_collector_disabled.php b/tests/integration/jit/function/test_span_events_error_collector_disabled.php new file mode 100644 index 000000000..09a26cd9c --- /dev/null +++ b/tests/integration/jit/function/test_span_events_error_collector_disabled.php @@ -0,0 +1,130 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/function/test_span_events_exception_caught_nested.php b/tests/integration/jit/function/test_span_events_exception_caught_nested.php new file mode 100644 index 000000000..2433f1289 --- /dev/null +++ b/tests/integration/jit/function/test_span_events_exception_caught_nested.php @@ -0,0 +1,217 @@ +getMessage() . "\n"); + } +} + +function fraction($x) { + time_nanosleep(0, 100000000); + if (!$x) { + throw new RuntimeException('Division by zero'); + } + return 1/$x; +} + +function a() +{ + time_nanosleep(0, 100000000); + echo "Hello\n"; +}; + +a(); +b(); diff --git a/tests/integration/jit/function/test_span_events_exception_caught_nested_rethrown.php b/tests/integration/jit/function/test_span_events_exception_caught_nested_rethrown.php new file mode 100644 index 000000000..036b6a55e --- /dev/null +++ b/tests/integration/jit/function/test_span_events_exception_caught_nested_rethrown.php @@ -0,0 +1,196 @@ +getMessage()); + } +} + +function fraction($x) { + time_nanosleep(0, 100000000); + if (!$x) { + throw new RuntimeException('Division by zero'); + } + return 1/$x; +} + +b(); diff --git a/tests/integration/jit/function/test_span_events_exception_caught_notice_error.php b/tests/integration/jit/function/test_span_events_exception_caught_notice_error.php new file mode 100644 index 000000000..202479b19 --- /dev/null +++ b/tests/integration/jit/function/test_span_events_exception_caught_notice_error.php @@ -0,0 +1,169 @@ +getMessage() . "\n"); + } + newrelic_notice_error(new Exception('Notice me')); +} + +function a() +{ + time_nanosleep(0, 100000000); + echo "Hello\n"; +}; + +a(); +fraction(); diff --git a/tests/integration/jit/function/test_span_events_exception_caught_notice_error_nested.php b/tests/integration/jit/function/test_span_events_exception_caught_notice_error_nested.php new file mode 100644 index 000000000..f182090d3 --- /dev/null +++ b/tests/integration/jit/function/test_span_events_exception_caught_notice_error_nested.php @@ -0,0 +1,245 @@ +getMessage() . "\n"); + } + newrelic_notice_error(new Exception('Notice me')); +} + +function fraction($x) { + time_nanosleep(0, 100000000); + if (!$x) { + throw new RuntimeException('Division by zero'); + } + return 1/$x; +} + +function a() +{ + time_nanosleep(0, 100000000); + echo "Hello\n"; +}; + +a(); +b(); diff --git a/tests/integration/jit/function/test_span_events_exception_caught_same_span.php b/tests/integration/jit/function/test_span_events_exception_caught_same_span.php new file mode 100644 index 000000000..943b908ba --- /dev/null +++ b/tests/integration/jit/function/test_span_events_exception_caught_same_span.php @@ -0,0 +1,139 @@ +getMessage() . "\n"); + } +} + +function a() +{ + time_nanosleep(0, 100000000); + echo "Hello\n"; +}; + +a(); +fraction(); diff --git a/tests/integration/jit/function/test_span_events_exception_uncaught_nested.php b/tests/integration/jit/function/test_span_events_exception_uncaught_nested.php new file mode 100644 index 000000000..d3c317994 --- /dev/null +++ b/tests/integration/jit/function/test_span_events_exception_uncaught_nested.php @@ -0,0 +1,224 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/function/test_span_events_exist_when_no_segments.php b/tests/integration/jit/function/test_span_events_exist_when_no_segments.php new file mode 100644 index 000000000..ce7cb296f --- /dev/null +++ b/tests/integration/jit/function/test_span_events_exist_when_no_segments.php @@ -0,0 +1,65 @@ +functionName(); + } +} + +for($i = 0; $i < 5; ++$i){ + new LittleClass; +} +echo "OK\n"; diff --git a/tests/integration/jit/tracing/test_span_events_are_created_from_segments.php b/tests/integration/jit/tracing/test_span_events_are_created_from_segments.php new file mode 100644 index 000000000..cc2a52753 --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_are_created_from_segments.php @@ -0,0 +1,118 @@ + 'FakeDB', +)); diff --git a/tests/integration/jit/tracing/test_span_events_are_created_upon_caught_error.php b/tests/integration/jit/tracing/test_span_events_are_created_upon_caught_error.php new file mode 100644 index 000000000..ebde4a26b --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_are_created_upon_caught_error.php @@ -0,0 +1,193 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/tracing/test_span_events_are_created_upon_exit.php b/tests/integration/jit/tracing/test_span_events_are_created_upon_exit.php new file mode 100644 index 000000000..176730a7f --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_are_created_upon_exit.php @@ -0,0 +1,123 @@ + 'FakeDB', + ) +); +a(); diff --git a/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_error.php b/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_error.php new file mode 100644 index 000000000..ee1a578ae --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_error.php @@ -0,0 +1,133 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_handled_exception.php b/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_handled_exception.php new file mode 100644 index 000000000..395b1231f --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_handled_exception.php @@ -0,0 +1,193 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_handled_exception_invalid_handler.php b/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_handled_exception_invalid_handler.php new file mode 100644 index 000000000..bd99855b8 --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_handled_exception_invalid_handler.php @@ -0,0 +1,205 @@ +=")) { + die("skip: PHP > 8.1 not supported\n"); +} + +require('skipif.inc'); + + +*/ + +/*INI +newrelic.distributed_tracing_enabled=1 +newrelic.transaction_tracer.threshold = 0 +newrelic.span_events_enabled=1 +newrelic.cross_application_tracer.enabled = false +error_reporting = E_ALL +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=32M +opcache.jit=tracing +*/ + +/*PHPMODULES +zend_extension=opcache.so +*/ + +/*EXPECT_ERROR_EVENTS +[ + "?? agent run id", + { + "reservoir_size": "??", + "events_seen": 1 + }, + [ + [ + { + "type": "TransactionError", + "timestamp": "??", + "error.class": "RuntimeException", + "error.message": "Uncaught exception 'RuntimeException' with message 'oops' in __FILE__:??", + "transactionName": "OtherTransaction\/php__FILE__", + "duration": "??", + "databaseDuration": "??", + "databaseCallCount": "??", + "nr.transactionGuid": "??", + "guid": "??", + "sampled": true, + "priority": "??", + "traceId": "??", + "spanId": "??" + }, + {}, + {} + ] + ] +] +*/ + +/*EXPECT_SPAN_EVENTS +[ + "?? agent run id", + { + "reservoir_size": 10000, + "events_seen": 4 + }, + [ + [ + { + "traceId": "??", + "duration": "??", + "transactionId": "??", + "name": "OtherTransaction\/php__FILE__", + "guid": "??", + "type": "Span", + "category": "generic", + "priority": "??", + "sampled": true, + "nr.entryPoint": true, + "timestamp": "??", + "transaction.name": "OtherTransaction\/php__FILE__" + }, + {}, + {} + ], + [ + { + "type": "Span", + "traceId": "??", + "transactionId": "??", + "sampled": true, + "priority": "??", + "name": "Datastore\/statement\/FakeDB\/other\/other", + "guid": "??", + "timestamp": "??", + "duration": "??", + "category": "datastore", + "parentId": "??", + "span.kind": "client", + "component": "FakeDB" + }, + {}, + { + "db.instance": "unknown", + "peer.hostname": "unknown", + "peer.address": "unknown:unknown" + } + ], + [ + { + "type": "Span", + "traceId": "??", + "transactionId": "??", + "sampled": true, + "priority": "??", + "name": "Custom\/a", + "guid": "??", + "timestamp": "??", + "duration": "??", + "category": "generic", + "parentId": "??" + }, + {}, + { + "error.message": "Uncaught exception 'RuntimeException' with message 'oops' in __FILE__:??", + "error.class": "RuntimeException", + "code.lineno": "??", + "code.filepath": "__FILE__", + "code.function": "a" + } + ], + [ + { + "type": "Span", + "traceId": "??", + "transactionId": "??", + "sampled": true, + "priority": "??", + "name": "Custom\/{closure}", + "guid": "??", + "timestamp": "??", + "duration": "??", + "category": "generic", + "parentId": "??" + }, + {}, + { + "code.lineno": "??", + "code.filepath": "__FILE__", + "code.function": "{closure}" + } + ] + ] +] +*/ + +/*EXPECT +*/ + +set_exception_handler( + function () { + time_nanosleep(0, 100000000); + exit(0); + } +); + +function a() +{ + time_nanosleep(0, 100000000); + throw new RuntimeException('oops'); +} + +newrelic_record_datastore_segment( + function () { + time_nanosleep(0, 100000000); + }, array( + 'product' => 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_unhandled_exception.php b/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_unhandled_exception.php new file mode 100644 index 000000000..54269f072 --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_are_created_upon_uncaught_unhandled_exception.php @@ -0,0 +1,170 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/tracing/test_span_events_error_collector_disabled.php b/tests/integration/jit/tracing/test_span_events_error_collector_disabled.php new file mode 100644 index 000000000..0e6fb6db1 --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_error_collector_disabled.php @@ -0,0 +1,131 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/tracing/test_span_events_exception_caught_nested.php b/tests/integration/jit/tracing/test_span_events_exception_caught_nested.php new file mode 100644 index 000000000..4ff854cf2 --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_exception_caught_nested.php @@ -0,0 +1,217 @@ +getMessage() . "\n"); + } +} + +function fraction($x) { + time_nanosleep(0, 100000000); + if (!$x) { + throw new RuntimeException('Division by zero'); + } + return 1/$x; +} + +function a() +{ + time_nanosleep(0, 100000000); + echo "Hello\n"; +}; + +a(); +b(); diff --git a/tests/integration/jit/tracing/test_span_events_exception_caught_nested_rethrown.php b/tests/integration/jit/tracing/test_span_events_exception_caught_nested_rethrown.php new file mode 100644 index 000000000..1ec6df51b --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_exception_caught_nested_rethrown.php @@ -0,0 +1,196 @@ +getMessage()); + } +} + +function fraction($x) { + time_nanosleep(0, 100000000); + if (!$x) { + throw new RuntimeException('Division by zero'); + } + return 1/$x; +} + +b(); diff --git a/tests/integration/jit/tracing/test_span_events_exception_caught_notice_error.php b/tests/integration/jit/tracing/test_span_events_exception_caught_notice_error.php new file mode 100644 index 000000000..5b42d5280 --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_exception_caught_notice_error.php @@ -0,0 +1,169 @@ +getMessage() . "\n"); + } + newrelic_notice_error(new Exception('Notice me')); +} + +function a() +{ + time_nanosleep(0, 100000000); + echo "Hello\n"; +}; + +a(); +fraction(); diff --git a/tests/integration/jit/tracing/test_span_events_exception_caught_notice_error_nested.php b/tests/integration/jit/tracing/test_span_events_exception_caught_notice_error_nested.php new file mode 100644 index 000000000..aaeb5a278 --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_exception_caught_notice_error_nested.php @@ -0,0 +1,246 @@ +getMessage() . "\n"); + } + newrelic_notice_error(new Exception('Notice me')); +} + +function fraction($x) { + time_nanosleep(0, 100000000); + if (!$x) { + throw new RuntimeException('Division by zero'); + } + return 1/$x; +} + +function a() +{ + time_nanosleep(0, 100000000); + echo "Hello\n"; +}; + +a(); +b(); diff --git a/tests/integration/jit/tracing/test_span_events_exception_caught_same_span.php b/tests/integration/jit/tracing/test_span_events_exception_caught_same_span.php new file mode 100644 index 000000000..d92e22d16 --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_exception_caught_same_span.php @@ -0,0 +1,139 @@ +getMessage() . "\n"); + } +} + +function a() +{ + time_nanosleep(0, 100000000); + echo "Hello\n"; +}; + +a(); +fraction(); diff --git a/tests/integration/jit/tracing/test_span_events_exception_uncaught_nested.php b/tests/integration/jit/tracing/test_span_events_exception_uncaught_nested.php new file mode 100644 index 000000000..d352b174e --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_exception_uncaught_nested.php @@ -0,0 +1,225 @@ + 'FakeDB', + ) +); +a(); + +echo 'this should never be printed'; diff --git a/tests/integration/jit/tracing/test_span_events_exist_when_no_segments.php b/tests/integration/jit/tracing/test_span_events_exist_when_no_segments.php new file mode 100644 index 000000000..a3c7c6a11 --- /dev/null +++ b/tests/integration/jit/tracing/test_span_events_exist_when_no_segments.php @@ -0,0 +1,65 @@ +