diff --git a/pom.xml b/pom.xml index c4461155f..6241dc806 100644 --- a/pom.xml +++ b/pom.xml @@ -77,14 +77,14 @@ 1.4.1 test - - - org.mockito - mockito-core - ${org.mockito.version} - test - - + org.assertj assertj-core @@ -167,6 +167,11 @@ 1.37 test + + com.vmlens + api + 1.2.11 + @@ -176,6 +181,7 @@ + @@ -206,7 +213,6 @@ pom import - @@ -298,7 +304,24 @@ - + + com.vmlens + vmlens-maven-plugin + 1.2.11 + + + test + + test + + + + + + **/VmLensTest.java + + + @@ -327,7 +350,8 @@ com.github.spotbugs:* org.junit* com.tngtech.archunit* - org.simplify4u:slf4j2-mock* + org.simplify4u:slf4j2-mock* + com.google.guava* diff --git a/sdk.iml b/sdk.iml new file mode 100644 index 000000000..9e3449c9d --- /dev/null +++ b/sdk.iml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/main/java/dev/openfeature/sdk/Awaitable.java b/src/main/java/dev/openfeature/sdk/Awaitable.java index 7d5f477dc..e74ef0e4e 100644 --- a/src/main/java/dev/openfeature/sdk/Awaitable.java +++ b/src/main/java/dev/openfeature/sdk/Awaitable.java @@ -10,7 +10,7 @@ public class Awaitable { */ public static final Awaitable FINISHED = new Awaitable(true); - private boolean isDone = false; + private volatile boolean isDone = false; public Awaitable() {} diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java index b5522b66a..64a0c6119 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java @@ -1,11 +1,14 @@ package dev.openfeature.sdk; +import com.vmlens.api.AllInterleavings; import dev.openfeature.sdk.exceptions.ExceptionUtils; import dev.openfeature.sdk.exceptions.FatalError; import dev.openfeature.sdk.exceptions.GeneralError; import dev.openfeature.sdk.exceptions.OpenFeatureError; import dev.openfeature.sdk.exceptions.ProviderNotReadyError; import dev.openfeature.sdk.internal.ObjectUtils; +import dev.openfeature.sdk.providers.memory.Flag; +import dev.openfeature.sdk.providers.memory.InMemoryProvider; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.ArrayList; import java.util.Arrays; @@ -15,6 +18,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import lombok.Getter; @@ -515,4 +519,117 @@ public Client removeHandler(ProviderEvent event, Consumer handler) openfeatureApi.removeHandler(domain, event, handler); return this; } + + static int jaVar = 0; + + /** + * run. + * + * @param args the args + * @throws InterruptedException something + */ + public static void main(String[] args) throws InterruptedException { + + System.out.println("OpenFeatureClientTest.testUpdate"); + final OpenFeatureAPI api = new OpenFeatureAPI(); + + var flags = new HashMap>(); + flags.put("a", Flag.builder().variant("a", "def").defaultVariant("a").build()); + flags.put("b", Flag.builder().variant("a", "as").defaultVariant("a").build()); + flags.put("c", Flag.builder().variant("a", "dfs").defaultVariant("a").build()); + flags.put("d", Flag.builder().variant("a", "asddd").defaultVariant("a").build()); + api.setProviderAndWait(new InMemoryProvider(flags)); + var i = 0; + var c = new AtomicInteger(); + + System.out.println("before try"); + try (AllInterleavings allInterleavings = new AllInterleavings("Concurrent evaluations and hook additions")) { + while (allInterleavings.hasNext()) { + c.incrementAndGet(); + jaVar = 0; + Thread first = new Thread() { + @Override + public void run() { + jaVar++; + } + }; + first.start(); + jaVar++; + first.join(); + if (jaVar != 2) { + throw new RuntimeException("jaVar=" + jaVar); + } + /* + var client = api.getClient(); + var firstReady = new Awaitable(); + var secondReady = new Awaitable(); + var startThreads = new Awaitable(); + Thread first = new Thread() { + @Override + public void run() { + firstReady.wakeup(); + startThreads.await(); + client.getStringValue("a", "a"); + } + }; + Thread hookAdder = new Thread() { + @Override + public void run() { + secondReady.wakeup(); + startThreads.await(); + client.addHooks(new Hook() {}); + } + }; + hookAdder.start(); + first.start(); + firstReady.await(); + secondReady.await(); + startThreads.wakeup(); + first.join(); + hookAdder.join(); +*/ + /*System.out.println("has interlereaving"); + i++; + //var latch = new CountDownLatch(1); + var client = api.getClient(); + var concurrentModException = new AtomicReference(); + Thread eval = new Thread(() -> { + System.out.println("eval"); + try { + latch.await(); + } catch (InterruptedException ignored) { + } + client.getStringValue("a", "a"); + client.getStringValue("b", "a"); + client.getStringValue("c", "a"); + client.getStringValue("d", "a"); + + }); + Thread hookAdder = new Thread(() -> { + System.out.println("hook adder"); + try { + latch.await(); + } catch (InterruptedException ignored) { + } + client.addHooks(new Hook() {}); + + }); + System.out.println("starting..."); + //eval.start(); + //hookAdder.start(); + System.out.println("started"); + Thread.sleep(200); + client.getStringValue("d", "a"); + client.addHooks(new Hook() {}); + client.getStringValue("d", "a"); + //latch.countDown(); + eval.join(); + hookAdder.join(); + System.out.println(concurrentModException.get());*/ + } + } + System.out.println("i = " + i); + System.out.println("jaVar = " + jaVar); + System.out.println("c.get() = " + c.get()); + } } diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java index 97a1417a1..ea9087255 100644 --- a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java +++ b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java @@ -2,16 +2,25 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import com.vmlens.api.AllInterleavings; import dev.openfeature.sdk.exceptions.FatalError; import dev.openfeature.sdk.fixtures.HookFixtures; +import dev.openfeature.sdk.providers.memory.Flag; +import dev.openfeature.sdk.providers.memory.InMemoryProvider; import dev.openfeature.sdk.testutils.TestEventsProvider; +import java.util.ConcurrentModificationException; import java.util.HashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -104,4 +113,64 @@ void shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState() { assertThat(details.getErrorCode()).isEqualTo(ErrorCode.PROVIDER_NOT_READY); } + + //@Test + void a() throws InterruptedException { + OpenFeatureAPI api = new OpenFeatureAPI(); + + var flags = new HashMap>(); + flags.put("a", Flag.builder().variant("a", "def").defaultVariant("a").build()); + flags.put("b", Flag.builder().variant("a", "as").defaultVariant("a").build()); + flags.put("c", Flag.builder().variant("a", "dfs").defaultVariant("a").build()); + flags.put("d", Flag.builder().variant("a", "asddd").defaultVariant("a").build()); + api.setProviderAndWait(new InMemoryProvider(flags)); + + var countDownLatch = new CountDownLatch(1); + var client = new OpenFeatureClient(api, "name", "version"); + var isRunning = new AtomicBoolean(true); + var eval = new Thread(() -> { + try { + countDownLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + System.out.println("starting eval"); + while (isRunning.get()) { + client.getStringValue("a", "def"); + client.getStringValue("b", "def"); + client.getStringValue("c", "def"); + client.getStringValue("d", "def"); + } + }); + var hook = new Thread(() -> { + try { + countDownLatch.await(); + Thread.sleep(5); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + System.out.println("starting hook"); + while (isRunning.get()) { + for (int i = 0; i < 100; i++) { + + client.addHooks(new Hook() {}); + } + + for (int i = 0; i < 90; i++) { + + client.getHooks().remove(4); + } + } + }); + + eval.start(); + hook.start(); + Thread.sleep(100); + countDownLatch.countDown(); + Thread.sleep(40000); + isRunning.set(false); + eval.join(); + hook.join(); + System.out.println(client.getHooks().size()); + } } diff --git a/src/test/java/dev/openfeature/sdk/VmLensTest.java b/src/test/java/dev/openfeature/sdk/VmLensTest.java new file mode 100644 index 000000000..59961dd87 --- /dev/null +++ b/src/test/java/dev/openfeature/sdk/VmLensTest.java @@ -0,0 +1,44 @@ +package dev.openfeature.sdk; + +import com.vmlens.api.AllInterleavings; +import com.vmlens.api.Runner; +import dev.openfeature.sdk.providers.memory.Flag; +import dev.openfeature.sdk.providers.memory.InMemoryProvider; +import org.junit.jupiter.api.Test; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Javadoc. + */ +public class VmLensTest { + public static void main(String[] args) throws InterruptedException { + new VmLensTest().asomeMethod(); + } + + @Test + public void asomeMethod() throws InterruptedException { + var c = new AtomicInteger(); + final OpenFeatureAPI api = new OpenFeatureAPI(); + + var flags = new HashMap>(); + flags.put("a", Flag.builder().variant("a", "def").defaultVariant("a").build()); + flags.put("b", Flag.builder().variant("a", "as").defaultVariant("a").build()); + flags.put("c", Flag.builder().variant("a", "dfs").defaultVariant("a").build()); + flags.put("d", Flag.builder().variant("a", "asddd").defaultVariant("a").build()); + api.setProviderAndWait(new InMemoryProvider(flags)); + + try (AllInterleavings allInterleavings = new AllInterleavings("Concurrent evaluations and hook additions")) { + while (allInterleavings.hasNext()) { + var client = api.getClient(); + c.incrementAndGet(); + Runner.runParallel( + () -> client.getStringValue("a", "a"), + () -> client.addHooks(new Hook() {}) + ); + } + } + api.shutdown(); + System.out.println("c = " + c); + } +} diff --git a/src/test/java/dev/openfeature/sdk/testutils/VmLensTest.java b/src/test/java/dev/openfeature/sdk/testutils/VmLensTest.java new file mode 100644 index 000000000..7256dacca --- /dev/null +++ b/src/test/java/dev/openfeature/sdk/testutils/VmLensTest.java @@ -0,0 +1,36 @@ +package dev.openfeature.sdk.testutils; + +import com.vmlens.api.AllInterleavings; +import org.junit.jupiter.api.Test; +import java.util.concurrent.atomic.AtomicInteger; + +class VmLensTest { + int j = 0; + + @Test + void concurrentEvalAndHooks() throws InterruptedException { + var c = new AtomicInteger(); + + try (AllInterleavings allInterleavings = new AllInterleavings("Concurrent evaluations and hook additions")) { + while (allInterleavings.hasNext()) { + c.incrementAndGet(); + j = 0; + Thread first = new Thread() { + @Override + public void run() { + j++; + } + }; + first.start(); + j++; + first.join(); + if (j != 2) { + throw new RuntimeException("j=" + j); + } + } + } + + System.out.println("c = " + c); + System.out.println("j = " + j); + } +} diff --git a/standalone/pom.xml b/standalone/pom.xml new file mode 100644 index 000000000..b4498316b --- /dev/null +++ b/standalone/pom.xml @@ -0,0 +1,137 @@ + + + 4.0.0 + + + com.vmlens + vmlens + 1.2.3 + + + standalone + + https://github.com/vmlens/vlmens + api to control vmlens + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + vmlens + https://www.vmlens.com + + + + + + Thomas Krieger + thomas.krieger@vmlens.com + vmlens + https://www.vmlens.com + + + + + scm:git:git@github.com:vmlens/vlmens.git + scm:git:git@github.com:vmlens/vlmens.git + git@github.com:vmlens/vlmens.git + + + + UTF-8 + + + + + com.vmlens + sync-bug + + + info.picocli + picocli + + + + + + + maven-clean-plugin + + + + src/main/resources/ + + **/*.jar + + + + + + + maven-resources-plugin + 3.3.1 + + + copy-resources + generate-sources + + copy-resources + + + src/main/resources/ + + + ../vmlens-maven-plugin/_dist + false + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + info.picocli + picocli-codegen + 4.7.7 + + + + -Aproject=${project.groupId}/${project.artifactId} + + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + com.vmlens.Standalone + + + + + + + + + + \ No newline at end of file diff --git a/standalone/src/main/java/com/vmlens/Install.java b/standalone/src/main/java/com/vmlens/Install.java new file mode 100644 index 000000000..6fe655c17 --- /dev/null +++ b/standalone/src/main/java/com/vmlens/Install.java @@ -0,0 +1,18 @@ +package com.vmlens; + +import com.vmlens.setup.EventDirectoryAndArgLine; +import com.vmlens.setup.Setup; +import picocli.CommandLine; + +@CommandLine.Command(name = "install", description = "Creates the agent directory. Print the vm parameter to System.out") +public class Install implements Runnable { + + @CommandLine.ParentCommand + private Standalone parent; + + @Override + public void run() { + EventDirectoryAndArgLine result = new Setup(parent.agentDirectory, "").setup(); + System.out.println("use " + result.argLine() + "as vm parameter"); + } +} diff --git a/standalone/src/main/java/com/vmlens/Report.java b/standalone/src/main/java/com/vmlens/Report.java new file mode 100644 index 000000000..10a7f9e1d --- /dev/null +++ b/standalone/src/main/java/com/vmlens/Report.java @@ -0,0 +1,29 @@ +package com.vmlens; + +import com.anarsoft.race.detection.main.ProcessEvents; +import com.vmlens.report.assertion.OnDescriptionAndLeftBeforeRightNoOp; +import com.vmlens.report.assertion.OnEventNoOp; +import picocli.CommandLine; + +import java.io.File; + +import static com.vmlens.setup.Setup.*; + +@CommandLine.Command(name = "report", description = "Creates the report.") +public class Report implements Runnable { + + @CommandLine.ParentCommand + private Standalone parent; + + @Override + public void run() { + File eventDirectory = new File(eventDir(parent.agentDirectory) ); + File reportDirectory = parent.reportDirectory; + + new ProcessEvents(eventDirectory.toPath(), + reportDirectory.toPath(), + new OnDescriptionAndLeftBeforeRightNoOp(), new OnEventNoOp()).process(); + + System.out.println("Report written to: " + reportDirectory.getAbsolutePath()); + } +} diff --git a/standalone/src/main/java/com/vmlens/Standalone.java b/standalone/src/main/java/com/vmlens/Standalone.java new file mode 100644 index 000000000..af4bc7c30 --- /dev/null +++ b/standalone/src/main/java/com/vmlens/Standalone.java @@ -0,0 +1,38 @@ +package com.vmlens; + +import picocli.CommandLine; + +import java.io.File; + +import static com.vmlens.Standalone.DESCRIPTION; +import static com.vmlens.setup.Setup.AGENT_DIRECTORY; +import static com.vmlens.setup.Setup.REPORT_DIRECTORY; + +@CommandLine.Command(name = "", description = DESCRIPTION, subcommands = { + Install.class, + Report.class +}) +public class Standalone { + + public static final String DESCRIPTION = "Run tests with vmlens.\n" + + "1) Call install to create the agent directory. This also prints the vm parameter to System.out.\n" + + "2) Run your test with the given vm parameter.\n" + + "3) Call report to create the reports and check for data races."; + + @CommandLine.Option(names = {"-a", "--agent"}, + defaultValue = AGENT_DIRECTORY, + description = "The agent directory. Default: " + AGENT_DIRECTORY) + File agentDirectory; + + @CommandLine.Option(names = {"-r", "--report"}, + defaultValue = REPORT_DIRECTORY, + description = "The report directory. Default: " + REPORT_DIRECTORY) + File reportDirectory; + + + public static void main(String[] args) { + int exitCode = new CommandLine(new Standalone()).execute(args); + System.exit(exitCode); + } + +} \ No newline at end of file diff --git a/standalone/src/main/resources/agent_lib/agent.jar b/standalone/src/main/resources/agent_lib/agent.jar new file mode 100644 index 000000000..c9fd33c96 Binary files /dev/null and b/standalone/src/main/resources/agent_lib/agent.jar differ diff --git a/standalone/src/main/resources/agent_lib/agent_bootstrap.jar b/standalone/src/main/resources/agent_lib/agent_bootstrap.jar new file mode 100644 index 000000000..92094cb47 Binary files /dev/null and b/standalone/src/main/resources/agent_lib/agent_bootstrap.jar differ diff --git a/standalone/src/main/resources/agent_lib/agent_runtime.jar b/standalone/src/main/resources/agent_lib/agent_runtime.jar new file mode 100644 index 000000000..753060c6f Binary files /dev/null and b/standalone/src/main/resources/agent_lib/agent_runtime.jar differ diff --git a/vmlens-agent/agent.jar b/vmlens-agent/agent.jar new file mode 100644 index 000000000..c9fd33c96 Binary files /dev/null and b/vmlens-agent/agent.jar differ diff --git a/vmlens-agent/agent_bootstrap.jar b/vmlens-agent/agent_bootstrap.jar new file mode 100644 index 000000000..92094cb47 Binary files /dev/null and b/vmlens-agent/agent_bootstrap.jar differ diff --git a/vmlens-agent/agent_runtime.jar b/vmlens-agent/agent_runtime.jar new file mode 100644 index 000000000..753060c6f Binary files /dev/null and b/vmlens-agent/agent_runtime.jar differ diff --git a/vmlens-agent/run.properties b/vmlens-agent/run.properties new file mode 100644 index 000000000..a717d6b7f --- /dev/null +++ b/vmlens-agent/run.properties @@ -0,0 +1,3 @@ +# +#Wed Jun 11 14:43:21 CEST 2025 +eventDir=Z\:\\Workspaces\\vmlens\\vmlens-agent/vmlens/ diff --git a/vmlens-agent/vmlens/agentlog.vmlens b/vmlens-agent/vmlens/agentlog.vmlens new file mode 100644 index 000000000..6c98d5b25 Binary files /dev/null and b/vmlens-agent/vmlens/agentlog.vmlens differ diff --git a/vmlens-agent/vmlens/control.vmlens b/vmlens-agent/vmlens/control.vmlens new file mode 100644 index 000000000..1970906b7 Binary files /dev/null and b/vmlens-agent/vmlens/control.vmlens differ diff --git a/vmlens-agent/vmlens/controlstatistic.vmlens b/vmlens-agent/vmlens/controlstatistic.vmlens new file mode 100644 index 000000000..492f766c5 Binary files /dev/null and b/vmlens-agent/vmlens/controlstatistic.vmlens differ diff --git a/vmlens-agent/vmlens/description.vmlens b/vmlens-agent/vmlens/description.vmlens new file mode 100644 index 000000000..e31784392 Binary files /dev/null and b/vmlens-agent/vmlens/description.vmlens differ diff --git a/vmlens-agent/vmlens/interleave.vmlens b/vmlens-agent/vmlens/interleave.vmlens new file mode 100644 index 000000000..6dca83452 Binary files /dev/null and b/vmlens-agent/vmlens/interleave.vmlens differ diff --git a/vmlens-agent/vmlens/interleavestatistic.vmlens b/vmlens-agent/vmlens/interleavestatistic.vmlens new file mode 100644 index 000000000..891d8a60b Binary files /dev/null and b/vmlens-agent/vmlens/interleavestatistic.vmlens differ diff --git a/vmlens-agent/vmlens/method.vmlens b/vmlens-agent/vmlens/method.vmlens new file mode 100644 index 000000000..b0143774d Binary files /dev/null and b/vmlens-agent/vmlens/method.vmlens differ diff --git a/vmlens-agent/vmlens/methodstatistic.vmlens b/vmlens-agent/vmlens/methodstatistic.vmlens new file mode 100644 index 000000000..70d481c51 Binary files /dev/null and b/vmlens-agent/vmlens/methodstatistic.vmlens differ diff --git a/vmlens-agent/vmlens/nonvolatile.vmlens b/vmlens-agent/vmlens/nonvolatile.vmlens new file mode 100644 index 000000000..5b43a6cfb Binary files /dev/null and b/vmlens-agent/vmlens/nonvolatile.vmlens differ diff --git a/vmlens-agent/vmlens/nonvolatilestatistic.vmlens b/vmlens-agent/vmlens/nonvolatilestatistic.vmlens new file mode 100644 index 000000000..15e4af6b2 Binary files /dev/null and b/vmlens-agent/vmlens/nonvolatilestatistic.vmlens differ diff --git a/vmlens-agent/vmlens/threadandloop.vmlens b/vmlens-agent/vmlens/threadandloop.vmlens new file mode 100644 index 000000000..5eebffa12 Binary files /dev/null and b/vmlens-agent/vmlens/threadandloop.vmlens differ