From 321a712ff86145bcba6cfb7598e0f1aaf2e8ea35 Mon Sep 17 00:00:00 2001 From: Andrei Pangin Date: Fri, 26 Jul 2024 14:41:40 +0300 Subject: [PATCH] Added Native API test --- test/one/profiler/test/Runner.java | 2 +- test/one/profiler/test/Test.java | 4 +- test/one/profiler/test/TestProcess.java | 83 +++++++++++++++++-------- test/test/c/CTests.java | 24 +++++++ test/test/c/nativeApi.c | 57 +++++++++++++++++ 5 files changed, 141 insertions(+), 29 deletions(-) create mode 100644 test/test/c/CTests.java create mode 100644 test/test/c/nativeApi.c diff --git a/test/one/profiler/test/Runner.java b/test/one/profiler/test/Runner.java index 7e94b183..233d8b7b 100644 --- a/test/one/profiler/test/Runner.java +++ b/test/one/profiler/test/Runner.java @@ -147,7 +147,7 @@ public class Runner { log.log(Level.INFO, "Running " + testName + "..."); String testLogDir = logDir.isEmpty() ? null : logDir + '/' + testName; - try (TestProcess p = new TestProcess(test, testLogDir, currentOs.getLibExt())) { + try (TestProcess p = new TestProcess(test, currentOs, testLogDir)) { Object holder = (m.getModifiers() & Modifier.STATIC) == 0 ? m.getDeclaringClass().newInstance() : null; m.invoke(holder, p); log.info("OK"); diff --git a/test/one/profiler/test/Test.java b/test/one/profiler/test/Test.java index a6deff82..47f5983b 100644 --- a/test/one/profiler/test/Test.java +++ b/test/one/profiler/test/Test.java @@ -16,7 +16,9 @@ import java.lang.annotation.Target; @Repeatable(Tests.class) public @interface Test { - Class mainClass(); + String[] sh() default {}; + + Class mainClass() default Test.class; String args() default ""; diff --git a/test/one/profiler/test/TestProcess.java b/test/one/profiler/test/TestProcess.java index eb90f618..2c5214e6 100644 --- a/test/one/profiler/test/TestProcess.java +++ b/test/one/profiler/test/TestProcess.java @@ -58,24 +58,10 @@ public class TestProcess implements Closeable { private final Map tmpFiles = new HashMap<>(); private final int timeout = 30; - public TestProcess(Test test, String logDir, String libExt) throws Exception { + public TestProcess(Test test, Os currentOs, String logDir) throws Exception { this.logDir = logDir; - List cmd = new ArrayList<>(); - cmd.add(System.getProperty("java.home") + "/bin/java"); - cmd.add("-cp"); - cmd.add(System.getProperty("java.class.path")); - if (test.debugNonSafepoints()) { - cmd.add("-XX:+UnlockDiagnosticVMOptions"); - cmd.add("-XX:+DebugNonSafepoints"); - } - addArgs(cmd, test.jvmArgs()); - if (!test.agentArgs().isEmpty()) { - cmd.add("-agentpath:build/lib/libasyncProfiler." + libExt + "=" + substituteFiles(test.agentArgs())); - } - cmd.add(test.mainClass().getName()); - addArgs(cmd, test.args()); - + List cmd = buildCommandLine(test, currentOs); log.log(Level.FINE, "Running " + cmd); ProcessBuilder pb = new ProcessBuilder(cmd).inheritIO(); @@ -87,8 +73,39 @@ public class TestProcess implements Closeable { } this.p = pb.start(); - // Give the JVM some time to initialize - Thread.sleep(700); + if (cmd.get(0).endsWith("java")) { + // Give the JVM some time to initialize + Thread.sleep(700); + } + } + + private List buildCommandLine(Test test, Os currentOs) { + List cmd = new ArrayList<>(); + + String[] sh = test.sh(); + if (sh.length > 0) { + cmd.add("/bin/sh"); + cmd.add("-e"); + cmd.add("-c"); + cmd.add(substituteFiles(String.join(";", sh))); + } else { + cmd.add(System.getProperty("java.home") + "/bin/java"); + cmd.add("-cp"); + cmd.add(System.getProperty("java.class.path")); + if (test.debugNonSafepoints()) { + cmd.add("-XX:+UnlockDiagnosticVMOptions"); + cmd.add("-XX:+DebugNonSafepoints"); + } + addArgs(cmd, test.jvmArgs()); + if (!test.agentArgs().isEmpty()) { + cmd.add("-agentpath:build/lib/libasyncProfiler." + currentOs.getLibExt() + "=" + + substituteFiles(test.agentArgs())); + } + cmd.add(test.mainClass().getName()); + addArgs(cmd, test.args()); + } + + return cmd; } private String getExtFromFile(File file) { @@ -124,7 +141,7 @@ public class TestProcess implements Closeable { StringBuffer sb = new StringBuffer(); do { - File f = createTempFile(m.group(1)); + File f = createTempFile(m.group(1), m.group(2)); m.appendReplacement(sb, f.getPath()); } while (m.find()); @@ -132,13 +149,17 @@ public class TestProcess implements Closeable { } private File createTempFile(String fileId) { - try { - File f = File.createTempFile("ap-" + fileId.substring(1), null); - tmpFiles.put(fileId, f); - return f; - } catch (IOException e) { - throw new UncheckedIOException(e); - } + return createTempFile(fileId, null); + } + + private File createTempFile(String fileId, String ext) { + return tmpFiles.computeIfAbsent(fileId, key -> { + try { + return File.createTempFile("ap-" + key.substring(1), ext); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); } private void clearTempFiles() { @@ -199,6 +220,10 @@ public class TestProcess implements Closeable { } } + public int exitCode() { + return p.exitValue(); + } + public Output waitForExit(String fileId) throws TimeoutException, InterruptedException { waitForExit(p, timeout); return readFile(fileId); @@ -239,8 +264,12 @@ public class TestProcess implements Closeable { return readFile(PROFOUT); } + public File getFile(String fileId) { + return tmpFiles.get(fileId); + } + public Output readFile(String fileId) { - File f = tmpFiles.get(fileId); + File f = getFile(fileId); try (Stream stream = Files.lines(f.toPath())) { return new Output(stream.toArray(String[]::new)); } catch (IOException e) { diff --git a/test/test/c/CTests.java b/test/test/c/CTests.java new file mode 100644 index 00000000..75371afb --- /dev/null +++ b/test/test/c/CTests.java @@ -0,0 +1,24 @@ +/* + * Copyright The async-profiler authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package test.c; + +import one.profiler.test.Os; +import one.profiler.test.Output; +import one.profiler.test.Test; +import one.profiler.test.TestProcess; + +public class CTests { + + // TODO: Make the test work on macOS + @Test(sh = {"gcc -Isrc test/test/c/nativeApi.c -ldl -o%c", "%c %f.jfr"}, output = true, os = Os.LINUX) + public void nativeApi(TestProcess p) throws Exception { + Output out = p.waitForExit(TestProcess.STDOUT); + assert p.exitCode() == 0; + assert out.contains("Starting profiler"); + assert out.contains("Stopping profiler"); + assert p.getFile("%f").length() > 0; + } +} diff --git a/test/test/c/nativeApi.c b/test/test/c/nativeApi.c new file mode 100644 index 00000000..c4a5b963 --- /dev/null +++ b/test/test/c/nativeApi.c @@ -0,0 +1,57 @@ +/* + * Copyright The async-profiler authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "asprof.h" + + +static void fail(const char* msg) { + fprintf(stderr, "%s\n", msg); + exit(1); +} + +static void uninterruptible_sleep(unsigned long ms) { + struct timespec ts = {ms / 1000, (ms % 1000) * 1000000}; + while (nanosleep(&ts, &ts) < 0 && errno == EINTR) ; +} + + +int main(int argc, char** argv) { + if (argc < 2) { + fail("Too few arguments"); + } + + void* lib = dlopen("build/lib/libasyncProfiler.so", RTLD_NOW); + if (lib == NULL) { + fail("Failed to load libasyncProfiler.so"); + } + + asprof_init_t asprof_init = dlsym(lib, "asprof_init"); + asprof_init(); + + char cmd[4096]; + snprintf(cmd, sizeof(cmd), "start,event=cpu,interval=1ms,wall=10ms,cstack=dwarf,loglevel=debug,file=%s", argv[1]); + + printf("Starting profiler\n"); + asprof_execute_t asprof_execute = dlsym(lib, "asprof_execute"); + asprof_error_t err = asprof_execute(cmd, NULL); + if (err != NULL) { + fail(err); + } + + uninterruptible_sleep(2000); + + printf("Stopping profiler\n"); + err = asprof_execute("stop", NULL); + if (err != NULL) { + fail(err); + } + + return 0; +}