Compare commits

...

180 Commits

Author SHA1 Message Date
renovate[bot] ac3344c7f6
chore(deps): update github/codeql-action digest to 7710ed1 (#1521)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 21:51:17 +00:00
renovate[bot] ecc8f7e3ad
chore(deps): update github/codeql-action digest to 03a2a17 (#1520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 16:35:35 +02:00
renovate[bot] cbf7a58622
chore(deps): update dependency maven to v3.9.11 (#1519)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 06:57:06 +00:00
renovate[bot] 1382b367d9
chore(deps): update actions/setup-java digest to ae2b61d (#1518)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 03:54:58 +00:00
renovate[bot] 5b3e3656f6
chore(deps): update github/codeql-action digest to 0d17ea4 (#1517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-15 23:46:35 +00:00
renovate[bot] 1d3fab6184
fix(deps): update dependency io.cucumber:cucumber-bom to v7.26.0 (#1516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 16:00:25 +02:00
renovate[bot] 006ae75e2b
chore(deps): update github/codeql-action digest to 6f936b5 (#1515)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 15:49:08 +02:00
renovate[bot] aa0569379b
chore(deps): update github/codeql-action digest to f53ec7c (#1512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 01:45:44 +00:00
renovate[bot] bf68cbdedf
fix(deps): update dependency io.cucumber:cucumber-bom to v7.25.0 (#1514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 20:57:58 +00:00
renovate[bot] 62738f7f16
chore(deps): update dependency com.diffplug.spotless:spotless-maven-plugin to v2.45.0 (#1509)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-08 10:17:05 +02:00
renovate[bot] 1e8f5c880c
chore(deps): update dependency com.google.guava:guava to v33.4.8-jre (#1382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-08 06:34:38 +00:00
renovate[bot] 488196656a
fix(deps): update dependency io.cucumber:cucumber-bom to v7.24.0 (#1510)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-08 08:28:26 +02:00
renovate[bot] 26716a51cf
chore(deps): update github/codeql-action digest to 624d0bc (#1507)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-08 03:14:51 +00:00
renovate[bot] 908755c2c2
chore(deps): update actions/setup-java digest to c190c18 (#1508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 23:04:40 +00:00
OpenFeature Bot d2b1dc3a41
chore(main): release 1.16.0 (#1452)
Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-07-07 14:54:59 -04:00
Todd Baert 85d89ee79a chore: update publish env vars
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-07 14:42:51 -04:00
Todd Baert 6194186b3e chore: skip tests on publish
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-07 14:39:17 -04:00
Todd Baert 5425a34a12 chore: migrate to new publish
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-07 14:30:18 -04:00
renovate[bot] 957c0d1ba3
fix(deps): update dependency org.junit:junit-bom to v5.13.3 (#1505)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 19:37:31 +00:00
chrfwow ebea0fdf1c
fix: Reduce locking and concurrency issues (#1478)
* Reduce locking and concurrency issues

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* Reduce locking and concurrency issues

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* formatting

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* use concurrent data structure for hooks

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* use concurrent data structure for hooks

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

---------

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 09:17:56 -04:00
renovate[bot] 08f549afd1
chore(deps): update actions/setup-java digest to 67aec00 (#1504)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 23:29:05 +00:00
renovate[bot] a5d1cbced4
chore(deps): update github/codeql-action digest to b694213 (#1503)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 19:13:17 +00:00
renovate[bot] 0fd9d3dcfb
chore(deps): update github/codeql-action digest to 33f8489 (#1502)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 00:11:36 +00:00
renovate[bot] 0515ad54c4
chore(deps): update dependency org.apache.maven.plugins:maven-gpg-plugin to v3.2.8 (#1501)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 13:05:20 +02:00
renovate[bot] 69519b1ef7
chore(deps): update github/codeql-action digest to dcc1a66 (#1499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 20:37:12 +00:00
renovate[bot] 2e3b479cb1
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10.26.1 (#1498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 01:58:05 +00:00
renovate[bot] 49214b7282
chore(deps): update github/codeql-action digest to 4c57370 (#1497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-27 23:59:31 +00:00
renovate[bot] fc430c3e1d
chore(deps): update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.3.2 (#1496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-27 07:35:50 +02:00
renovate[bot] 86a5916f0d
chore(deps): update github/codeql-action digest to 8ef1782 (#1495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 14:54:48 +02:00
renovate[bot] 34b22e8d93
fix(deps): update dependency org.junit:junit-bom to v5.13.2 (#1492)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 06:40:04 +00:00
renovate[bot] 300a705e0a
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10.26.0 (#1494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 08:32:52 +02:00
renovate[bot] b64efe82d9
chore(deps): update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.3.1 (#1493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 02:49:37 +00:00
renovate[bot] 6f67b06f71
chore(deps): update github/codeql-action digest to 9b02dc2 (#1491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 20:50:48 +00:00
renovate[bot] e67f598357
chore(deps): update actions/setup-java digest to ebb356c (#1490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 22:42:58 +00:00
renovate[bot] 312b6df5d2
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10.25.1 (#1489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-21 21:10:50 +00:00
renovate[bot] 8fad544b17
chore(deps): update github/codeql-action digest to ac30a39 (#1488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-20 19:08:37 +00:00
renovate[bot] c3eaecdb8b
chore(deps): update github/codeql-action digest to 66d7255 (#1487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-20 01:25:24 +00:00
renovate[bot] 7c2af57a36
chore(deps): update actions/cache digest to 640a1c2 (#1485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-17 20:05:59 +00:00
renovate[bot] 8bf777a7e9
chore(deps): update github/codeql-action digest to ef36b69 (#1484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-17 04:48:36 +00:00
renovate[bot] 936ff60fac
chore(deps): update dependency net.bytebuddy:byte-buddy-agent to v1.17.6 (#1483)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 16:50:34 +00:00
renovate[bot] 99a3006de8
chore(deps): update github/codeql-action digest to 3de706a (#1481)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 16:44:00 +00:00
renovate[bot] 8e51e6fe10
chore(deps): update dependency net.bytebuddy:byte-buddy to v1.17.6 (#1482)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 18:37:11 +02:00
renovate[bot] 844d5e244b
chore(deps): update github/codeql-action digest to be30325 (#1479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-13 18:52:24 +00:00
renovate[bot] 0b57bcafc1
chore(deps): update github/codeql-action digest to 466d6ce (#1477)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 21:43:28 +00:00
chrfwow 3dd7d5d426
feat: add means of awaiting event emission, fix flaky build (#1463)
## This PR

- adds the ability to await event emissions by returning a new construct
- uses this construct in tests

⚠️ in rare cases, this could be a breaking change, but the events API is currently experimental, so we will not do a major version ubmp

### Related Issues

Fixes #1449
2025-06-10 11:27:15 -04:00
renovate[bot] 6cca721be5
chore(deps): update github/codeql-action digest to 7cb9b16 (#1476)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 01:27:44 +00:00
renovate[bot] 4481537ceb
chore(deps): update dependency maven to v3.9.10 (#1474)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 21:59:01 +00:00
renovate[bot] b5d873e44d
chore(deps): update actions/checkout digest to 09d2aca (#1473)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 17:39:13 +00:00
renovate[bot] 545d6aac09
fix(deps): update dependency org.junit:junit-bom to v5.13.1 (#1475)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 13:08:10 +00:00
renovate[bot] 2dcd6a1dd0
chore(deps): update github/codeql-action digest to b1e4dc3 (#1471)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-05 06:27:45 +00:00
Simon Schrottner 3ed65cfb0c
chore: remove unneeded version information (#1428)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-06-04 14:44:38 +00:00
renovate[bot] 6597de7a98
chore(deps): update github/codeql-action digest to 075e08a (#1470)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-03 18:05:22 +00:00
renovate[bot] 376f81f5c3
chore(deps): update github/codeql-action digest to 4a00331 (#1469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 19:28:18 +00:00
renovate[bot] 1558a86249
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10.25.0 (#1468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-31 21:27:57 +00:00
renovate[bot] f8260a1c3a
fix(deps): update junit5 monorepo (#1467)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 21:02:38 +00:00
renovate[bot] 50a6b168a7
fix(deps): update dependency io.cucumber:cucumber-bom to v7.23.0 (#1466)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 05:38:25 +00:00
renovate[bot] b6ceff2ecb
chore(deps): update dependency org.codehaus.mojo:exec-maven-plugin to v3.5.1 (#1461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 02:01:25 +00:00
renovate[bot] 2de7616676
chore(deps): update io.cucumber.version to v7.23.0 (#1465)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 23:00:03 +00:00
renovate[bot] f10aaaa357
chore(deps): update github/codeql-action digest to 7fd6215 (#1464)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 22:25:40 +00:00
renovate[bot] 40b319c5de
chore(deps): update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.5 (#1462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 04:12:52 +00:00
renovate[bot] 5e922cf3ef
chore(deps): update github/codeql-action digest to bc02a25 (#1460)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 22:26:25 +00:00
renovate[bot] 6a95c008e9
chore(deps): update github/codeql-action digest to 7b0fb5a (#1459)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-22 22:59:22 +00:00
renovate[bot] dcbfd265a3
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10.24.0 (#1458)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-22 06:29:34 +00:00
renovate[bot] e17b0b2975
chore(deps): update dependency org.mockito:mockito-core to v5.18.0 (#1457)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-21 04:08:45 +00:00
renovate[bot] b45a937017
chore(deps): update github/codeql-action digest to 396fd27 (#1456)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 23:54:03 +00:00
renovate[bot] 36eed065e7
chore(deps): update github/codeql-action digest to 57eebf6 (#1455)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 15:06:06 +00:00
renovate[bot] e3379395e6
chore(deps): update codecov/codecov-action action to v5.4.3 (#1454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 01:40:23 +00:00
renovate[bot] b667aa3251
chore(deps): update github/codeql-action digest to b86edfc (#1453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 20:01:17 +00:00
Todd Baert 1714efe81a
chore: improvements to release workflow (#1451)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: chrfwow <christian.lutnik@dynatrace.com>
2025-05-15 11:22:10 -04:00
renovate[bot] d9a72d2aaf
chore(deps): update github/codeql-action digest to 510dfa3 (#1450)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-14 22:43:05 +00:00
OpenFeature Bot cfd9512728
chore(main): release 1.15.1 (#1448)
* chore(main): release 1.15.1

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>

* fixup: change version

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>

---------

Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-05-14 14:12:11 +02:00
Todd Baert f6bd30db93
chore: update boostrap sha for release please
Creating a new build

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-05-14 07:57:13 -04:00
chrfwow e2813b2e5d
feat: add logging on provider state transitions (#1444)
* NOISSUE add logging on provider state transitions

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* fix npe

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* fix failing test

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* fix failing test

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* format

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

---------

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-05-14 11:11:36 +02:00
renovate[bot] bc10bacb5a
chore(deps): update github/codeql-action digest to 15bce5b (#1443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 22:40:47 +00:00
OpenFeature Bot 7182a7fc41
chore(main): release 1.15.0 (#1431)
Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>
2025-05-13 12:38:55 -04:00
Liran M d0ae548277
feat: add telemetry helper utils (#1346)
* feat: add telemetry helper utils

Signed-off-by: liran2000 <liran2000@gmail.com>

* updates

Signed-off-by: liran2000 <liran2000@gmail.com>

* fixup: apply changes according to the semconv

The semconv has changed, and some attributes have been
renamed. Furthermore, the body usage is deprecated
and should be part of the attributes.

see: https://github.com/open-telemetry/semantic-conventions/pull/1990/
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>

* fixup: fix tests

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>

* fixup: fix spotless

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>

---------

Signed-off-by: liran2000 <liran2000@gmail.com>
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-05-13 09:45:16 +02:00
renovate[bot] e568f3a4f5
fix(deps): update dependency io.cucumber:cucumber-bom to v7.22.2 (#1442)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-12 07:07:53 +00:00
renovate[bot] 58454b4eaa
chore(deps): update io.cucumber.version to v7.22.2 (#1441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-12 09:01:29 +02:00
renovate[bot] 78657ee79e
chore(deps): update dependency com.tngtech.archunit:archunit-junit5 to v1.4.1 (#1440)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-08 03:06:04 +00:00
renovate[bot] 3403510515
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10 (#103)
* chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10

* Fix javadoc format

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

---------

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: christian.lutnik <christian.lutnik@dynatrace.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-05-06 06:19:41 +00:00
jarebudev 4dc988b637
feat!: Raise required Java version to 11 (#1393)
* bumped to java 11, altered CI to target java 11

Signed-off-by: jarebudev <23311805+jarebudev@users.noreply.github.com>

* changed profile and toolchain

Signed-off-by: jarebudev <23311805+jarebudev@users.noreply.github.com>

* corrected toolchain version

Signed-off-by: jarebudev <23311805+jarebudev@users.noreply.github.com>

---------

Signed-off-by: jarebudev <23311805+jarebudev@users.noreply.github.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-05-05 09:30:23 +00:00
renovate[bot] f2348ea370
chore(deps): update github/codeql-action digest to 5eb3ed6 (#1439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 18:13:36 +00:00
renovate[bot] 85b200a08b
chore(deps): update github/codeql-action digest to 97a2bfd (#1438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-01 18:38:45 +00:00
renovate[bot] f965cbcb37
chore(deps): update github/codeql-action digest to 40e16ed (#1437)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-30 23:46:35 +00:00
renovate[bot] b09e88798f
chore(deps): update github/codeql-action digest to ed51cb5 (#1436)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 19:28:15 +00:00
renovate[bot] 7e74f2aa3a
chore(deps): update github/codeql-action digest to 83605b3 (#1435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 02:56:28 +00:00
renovate[bot] 62ba6db457
chore(deps): update amannn/action-semantic-pull-request digest to 3352882 (#1434)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 20:32:23 +00:00
Abhay Porwal 96cf9c7f54
docs: add try-catch example for setProviderAndWait usage (#1433)
* added the detailed instructions for the setProviderAndWait

Signed-off-by: Abhay <abhayakg123@gmail.com.com>

* fixup: checkstyle issues

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>

---------

Signed-off-by: Abhay <abhayakg123@gmail.com.com>
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Abhay <abhayakg123@gmail.com.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-04-28 14:34:19 +02:00
renovate[bot] 99faaf88aa
chore(deps): update github/codeql-action digest to f843d94 (#1432)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-25 22:07:09 +00:00
Todd Baert 1cc851b293
chore: update release please action (#1430)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-24 21:41:37 -04:00
Todd Baert 32137bfa82
chore: add DCO to release please (#1429)
* chore: add DCO to release please

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* Update release-please-config.json

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-24 21:14:07 -04:00
Todd Baert 45ec4b1b77
chore: add DCO to release please
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-24 20:58:47 -04:00
Todd Baert 014f8a59da
chore: use PAT for release please
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-24 19:32:45 -04:00
renovate[bot] 1c4d2efafd
fix(deps): update dependency io.cucumber:cucumber-bom to v7.22.1 (#1427)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-24 18:19:32 +00:00
renovate[bot] 844374a42b
chore(deps): update io.cucumber.version to v7.22.1 (#1426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-24 15:19:34 +00:00
renovate[bot] a7828e73a8
chore(deps): update github/codeql-action digest to 4ffa236 (#1425)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-23 22:47:18 +00:00
renovate[bot] 6b6849f3a3
chore(deps): update github/codeql-action digest to 2a8cbad (#1423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-22 19:25:23 +00:00
renovate[bot] 495da271be
chore(deps): update dependency com.h3xstream.findsecbugs:findsecbugs-plugin to v1.14.0 (#1422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-20 10:02:26 +00:00
renovate[bot] e19ccaa35d
chore(deps): update dependency org.jacoco:jacoco-maven-plugin to v0.8.13 (#1407)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-18 08:15:55 +02:00
Simon Schrottner 498fd38265
chore: update codeowners to give global maintainers code ownership (#1412)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-04-17 11:33:57 -04:00
renovate[bot] a3e2a59aeb
chore(deps): update actions/setup-java digest to f4f1212 (#1421)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-16 21:41:52 +00:00
Todd Baert 665dd51eb2
chore: add publish env (#1420)
* chore: add publish env

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* Update release.yml

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* Update merge.yml

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-16 12:45:16 -04:00
renovate[bot] a6389e89f6
chore(deps): update codecov/codecov-action action to v5.4.2 (#1419)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 03:44:53 +00:00
renovate[bot] 97b442ed6e
fix(deps): update junit5 monorepo (#1418)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 21:45:27 +00:00
renovate[bot] 0c77c84460
chore(deps): update github/codeql-action digest to 4c3e536 (#1417)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 02:21:54 +00:00
renovate[bot] 4607c62f15
chore(deps): update github/codeql-action digest to 56dd02f (#1416)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-09 22:09:23 +00:00
renovate[bot] a5789038ac
chore(deps): update actions/setup-java digest to c5195ef (#1415)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 23:02:50 +00:00
renovate[bot] e066d3f749
chore(deps): update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.4 (#1414)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 06:34:39 +00:00
renovate[bot] 5b327eeb77
chore(deps): update github/codeql-action digest to d26c46a (#1413)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 01:31:00 +00:00
renovate[bot] e25181982a
fix(deps): update dependency io.cucumber:cucumber-bom to v7.22.0 (#1411)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 10:23:07 +00:00
renovate[bot] 3c69f2f36c
chore(deps): update io.cucumber.version to v7.22.0 (#1410)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 06:25:16 +00:00
renovate[bot] 345cdcfa10
chore(deps): update dependency org.mockito:mockito-core to v5.17.0 (#1409)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-04 18:42:05 +00:00
renovate[bot] ca160cab7c
chore(deps): update github/codeql-action digest to 362ef4c (#1408)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 18:12:31 +00:00
renovate[bot] e211397d51
chore(deps): update github/codeql-action digest to e13fe0d (#1406)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 02:18:17 +00:00
renovate[bot] 5b2f1513ab
chore(deps): update github/codeql-action digest to dab8a02 (#1405)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 17:47:53 +00:00
renovate[bot] f834e11acc
chore(deps): update actions/setup-java digest to 148017a (#1404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 22:21:36 +00:00
renovate[bot] ef32f11571
fix(deps): update dependency org.projectlombok:lombok to v1.18.38 (#1403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 03:02:58 +00:00
renovate[bot] 07301bda3f
chore(deps): update dependency net.bytebuddy:byte-buddy-agent to v1.17.5 (#1401)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 23:11:43 +00:00
renovate[bot] 384953d30e
chore(deps): update github/codeql-action digest to efffb48 (#1402)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 18:29:21 +00:00
renovate[bot] 1f2d071508
chore(deps): update dependency net.bytebuddy:byte-buddy to v1.17.5 (#1400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 15:00:52 +00:00
renovate[bot] d6ebc161a9
chore(deps): update dependency org.apache.maven.plugins:maven-surefire-plugin to v3.5.3 (#1399)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 10:47:07 +00:00
renovate[bot] 1fcf0e77d9
chore(deps): update dependency org.apache.maven.plugins:maven-failsafe-plugin to v3.5.3 (#1398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 05:38:29 +00:00
renovate[bot] 37d76be697
chore(deps): update github/codeql-action digest to 9f45e74 (#1396)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 23:26:09 +00:00
renovate[bot] d7b591c9f9
chore(deps): update github/codeql-action digest to 9bd18b4 (#1394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-27 23:40:23 +00:00
github-actions[bot] 7f54c334da
chore(main): release 1.14.2 (#1334)
* chore(main): release 1.15.0

* Update CHANGELOG.md

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* Update .release-please-manifest.json

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* Update CHANGELOG.md

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* Update version.txt

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* Update README.md

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* Update README.md

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* Update pom.xml

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-27 15:48:06 -04:00
Todd Baert 24ef9dd290
fix: hooks not run in NOT_READY/FATAL (#1392)
* fix: hooks not run in NOT_READY/FATAL

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-27 09:22:19 -04:00
renovate[bot] 753667925a
chore(deps): update actions/setup-java digest to 3b6c050 (#1391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 07:41:23 +00:00
renovate[bot] 87c06d9edd
chore(deps): update amannn/action-semantic-pull-request digest to 04501d4 (#1390)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 16:35:34 +00:00
renovate[bot] 85fd5e0997
chore(deps): update github/codeql-action digest to 486ab5a (#1389)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 02:02:27 +00:00
renovate[bot] d8f6514598
chore(deps): update dependency net.bytebuddy:byte-buddy-agent to v1.17.4 (#1388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 13:00:33 +00:00
renovate[bot] cb574d93b6
chore(deps): update dependency net.bytebuddy:byte-buddy to v1.17.4 (#1387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 07:03:02 +00:00
renovate[bot] 4125ae8380
chore(deps): update dependency net.bytebuddy:byte-buddy-agent to v1.17.3 (#1385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 10:02:36 +00:00
renovate[bot] 387e5f2e3b
chore(deps): update github/codeql-action digest to e0ea141 (#1386)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 05:11:28 +00:00
renovate[bot] b6becac2c4
chore(deps): update dependency net.bytebuddy:byte-buddy to v1.17.3 (#1384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 03:00:07 +00:00
renovate[bot] 922e17e677
chore(deps): update github/codeql-action digest to bd1d9ab (#1383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 22:54:41 +00:00
renovate[bot] d61c33e466
chore(deps): update github/codeql-action digest to c50c157 (#1379)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 07:07:31 +00:00
renovate[bot] 2239f054b9
chore(deps): update actions/setup-java digest to b8ebb8b (#1381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 01:33:47 +00:00
renovate[bot] 8359ef13bb
chore(deps): update actions/cache digest to 5a3ec84 (#1380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-19 21:41:15 +00:00
renovate[bot] dbf92df33b
chore(deps): update github/codeql-action digest to 6349095 (#1378)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 18:41:01 +00:00
renovate[bot] 706565581d
chore(deps): update github/codeql-action digest to 6a151cd (#1377)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 02:01:21 +00:00
renovate[bot] 9750f75d04
chore(deps): update dependency org.mockito:mockito-core to v5.16.1 (#1376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-16 05:21:02 +00:00
renovate[bot] de3e213ac8
chore(deps): update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.3.0 (#1375)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-16 02:24:15 +00:00
renovate[bot] 6b65e26c74
fix(deps): update junit5 monorepo (#1373)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-15 06:28:22 +00:00
renovate[bot] d233480912
chore(deps): update github/codeql-action digest to 70df9de (#1372)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-15 01:29:33 +00:00
chrfwow 69b571eda7
fix: equals and hashcode of several classes (#1364)
fix: equals and hashcode of several classes
2025-03-13 08:33:21 +01:00
renovate[bot] f8df5fb84a
chore(deps): update github/codeql-action digest to dc49dca (#1369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-12 03:22:53 +00:00
renovate[bot] d54c68a8e9
chore(deps): update github/codeql-action digest to 7254660 (#1368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-11 23:00:23 +00:00
renovate[bot] c550d59722
chore(deps): update github/codeql-action digest to b46b37a (#1367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 22:21:34 +00:00
renovate[bot] d00e4b5b24
chore(deps): update github/codeql-action digest to b2e6519 (#1366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 22:43:13 +00:00
chrfwow c37d249776
feat: implement gherkin tests for context merging (#1363)
feat: implement gherkin tests for context merging (#1363)
2025-03-07 11:04:46 +01:00
renovate[bot] 959e675e4c
chore(deps): update github/codeql-action digest to 56b25d5 (#1365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-06 19:34:13 +00:00
renovate[bot] 67b34f84a3
chore(deps): update github/codeql-action digest to 608ccd6 (#1361)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 19:07:16 +00:00
renovate[bot] 30b6d004aa
chore(deps): update dependency org.mockito:mockito-core to v5.16.0 (#1358)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 15:10:03 +00:00
renovate[bot] ecea9df932
chore(deps): update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.2.0 (#1360)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 10:30:29 +00:00
renovate[bot] 6c03e5d84a
chore(deps): update github/codeql-action digest to 80f9930 (#1357)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 06:25:17 +00:00
renovate[bot] 31444d6c8f
chore(deps): update actions/setup-java digest to 799ee7c (#1359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 02:12:10 +00:00
renovate[bot] dd83114c4d
chore(deps): update dependency net.bytebuddy:byte-buddy-agent to v1.17.2 (#1356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 23:33:51 +00:00
renovate[bot] 2a1adca8c2
chore(deps): update dependency net.bytebuddy:byte-buddy to v1.17.2 (#1355)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 17:40:45 +00:00
renovate[bot] 59017977a4
chore(deps): update actions/cache digest to d4323d4 (#1353)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 18:39:13 +00:00
renovate[bot] 989f4ae542
chore(deps): update github/codeql-action digest to 8392354 (#1352)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 16:13:25 +00:00
renovate[bot] b133c2fa52
chore(deps): update codecov/codecov-action action to v5.4.0 (#1351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 06:59:17 +00:00
renovate[bot] 7df9565691
chore(deps): update github/codeql-action digest to 97aac9b (#1350)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 23:30:46 +00:00
renovate[bot] 2ec7c6c7ff
fix(deps): update dependency org.slf4j:slf4j-api to v2.0.17 (#1348)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 02:09:26 +00:00
renovate[bot] 698756856b
chore(deps): update github/codeql-action digest to 8c69433 (#1347)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 21:49:59 +00:00
renovate[bot] 5de33c02a6
chore(deps): update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.3 (#1341)
* chore(deps): update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.3

* fixup: fix spotless violation

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>

---------

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-02-25 08:15:15 +01:00
renovate[bot] de64eddfb3
chore(deps): update github/codeql-action digest to a8849fb (#1345)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 06:00:41 +00:00
renovate[bot] d95e270653
fix(deps): update junit5 monorepo (#1344)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 09:50:37 +00:00
renovate[bot] 1504d0f798
chore(deps): update dependency org.awaitility:awaitility to v4.3.0 (#1343)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 07:00:59 +00:00
renovate[bot] 88a778cc03
chore(deps): update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.14.0 (#1342)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 01:27:57 +00:00
renovate[bot] 50b45b2be4
chore(deps): update github/codeql-action digest to ff79de6 (#1340)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 21:38:42 +00:00
chrfwow dd9227a924
feat: update test harness with metadata assertions #1467 (#1319)
* feat: implement gherkin tests

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* fixup! feat: implement gherkin tests

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* fixup! feat: implement gherkin tests

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* fixup! feat: implement gherkin tests

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* fixup: add version for dependency

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>

---------

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-02-19 16:27:46 +01:00
renovate[bot] cdcdc143ea
chore(deps): update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.1.0 (#1332)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-19 10:06:15 +00:00
renovate[bot] 3920c638a4
chore(deps): update actions/cache digest to 7921ae2 (#1337)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-19 06:22:52 +00:00
renovate[bot] 4e535fd10f
chore(deps): update github/codeql-action digest to d99c7e8 (#1338)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-19 02:13:36 +00:00
renovate[bot] 4817864fd7
chore(deps): update dependency org.mockito:mockito-core to v5.15.2 (#1339)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 22:10:34 +01:00
Simon Schrottner 90217b2083
chore: update build and tooling to utilize new java version (#1321)
* chore: update build and tooling to utilize new java version

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>

* Fix failing test

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>

* chore: update build and tooling to utilize new java version

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>

---------

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>
Co-authored-by: christian.lutnik <christian.lutnik@dynatrace.com>
2025-02-18 21:58:17 +01:00
Simon Schrottner 88baa65dd0
test: Reduce usage of singelton within our tests and implementations (#1331)
Our tests are great, but often we rely on our own Singelton for testing purposes.
This can create concurrency issues or make testing really hard.
By instantiating a own API object for each test we ensure that we are not messing
with each other.
Furthermore we should not use `.getInstance()` within our own implementation.

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-02-18 19:19:18 +01:00
renovate[bot] e163ce1c06
chore(deps): update github/codeql-action digest to 1bb15d0 (#1336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 14:48:26 +00:00
renovate[bot] 5436eb0d5d
chore(deps): update github/codeql-action digest to acadfed (#1335)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 02:28:02 +00:00
renovate[bot] 859a36cbfa
chore(deps): update github/codeql-action digest to 8c1551c (#1333)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 18:05:19 +00:00
77 changed files with 2410 additions and 799 deletions

1
.gitattributes vendored
View File

@ -3,4 +3,3 @@
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

View File

@ -18,6 +18,6 @@ jobs:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@40166f00814508ec3201fc8595b393d451c8cd80
- uses: amannn/action-semantic-pull-request@335288255954904a41ddda8947c8f2c844b8bfeb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -9,35 +9,36 @@ name: on-merge
on:
push:
branches: [ master, main ]
branches:
- main
permissions:
contents: read
jobs:
build:
environment: publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- name: Set up JDK 8
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
- name: Set up JDK 17
uses: actions/setup-java@ae2b61dbc685e60e4427b2e8ed4f0135c6ea8597
with:
java-version: '8'
java-version: '17'
distribution: 'temurin'
cache: maven
server-id: ossrh
server-username: ${{ secrets.OSSRH_USERNAME }}
server-password: ${{ secrets.OSSRH_PASSWORD }}
server-id: central
server-username: ${{ secrets.CENTRAL_USERNAME }}
server-password: ${{ secrets.CENTRAL_PASSWORD }}
- name: Cache local Maven repository
uses: actions/cache@9fa7e61ec7e1f44ac75218e7aaea81da8856fd11
uses: actions/cache@640a1c2554105b57832a23eea0b4672fc7a790d5
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
key: ${{ runner.os }}-17-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
${{ runner.os }}-17-maven-
- name: Configure GPG Key
run: |
@ -49,7 +50,7 @@ jobs:
run: mvn --batch-mode --update-snapshots verify
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.3.1
uses: codecov/codecov-action@v5.4.3
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
flags: unittests # optional
@ -60,11 +61,11 @@ jobs:
# Add -SNAPSHOT before deploy
- name: Add SNAPSHOT
run: mvn versions:set -DnewVersion='${project.version}-SNAPSHOT'
- name: Deploy
run: |
mvn --batch-mode \
--settings release/m2-settings.xml clean deploy
--settings release/m2-settings.xml -DskipTests clean deploy
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}

View File

@ -7,36 +7,46 @@ permissions:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest]
build:
- java: 17
profile: codequality
- java: 11
profile: java11
name: with Java ${{ matrix.build.java }}
runs-on: ${{ matrix.os}}
steps:
- name: Check out the code
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
- name: Set up JDK 8
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
- name: Set up JDK 11
uses: actions/setup-java@ae2b61dbc685e60e4427b2e8ed4f0135c6ea8597
with:
java-version: '8'
distribution: 'temurin'
cache: maven
java-version: ${{ matrix.build.java }}
distribution: 'temurin'
cache: maven
- name: Initialize CodeQL
uses: github/codeql-action/init@1c15a48f3fb49ce535e9ee4e57e127315f669361
uses: github/codeql-action/init@7710ed11e398ea99c7f7004c2b2e0f580458db42
with:
languages: java
- name: Cache local Maven repository
uses: actions/cache@9fa7e61ec7e1f44ac75218e7aaea81da8856fd11
uses: actions/cache@640a1c2554105b57832a23eea0b4672fc7a790d5
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
path: ~/.m2/repository
key: ${{ runner.os }}${{ matrix.build.java }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}${{ matrix.build.java }}-maven-
- name: Verify with Maven
run: mvn --batch-mode --update-snapshots --activate-profiles e2e verify
run: mvn --batch-mode --update-snapshots --activate-profiles e2e,${{ matrix.build.profile }} verify
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.3.1
- if: matrix.build.java == '17'
name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.4.3
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
flags: unittests # optional
@ -45,4 +55,4 @@ jobs:
verbose: true # optional (default = false)
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@1c15a48f3fb49ce535e9ee4e57e127315f669361
uses: github/codeql-action/analyze@7710ed11e398ea99c7f7004c2b2e0f580458db42

View File

@ -12,46 +12,53 @@ permissions: # added using https://github.com/step-security/secure-workflows
jobs:
release-please:
permissions:
contents: write # for google-github-actions/release-please-action to create release commit
pull-requests: write # for google-github-actions/release-please-action to create release PR
runs-on: ubuntu-latest
permissions:
contents: write # for googleapis/release-please-action to create release commit
pull-requests: write # for googleapis/release-please-action to create release PR
issues: write # for googleapis/release-please-action to create labels
# Release-please creates a PR that tracks all changes
steps:
- uses: google-github-actions/release-please-action@e4dc86ba9405554aeba3c6bb2d169500e7d3b4ee
- uses: googleapis/release-please-action@v4
id: release
with:
token: ${{secrets.GITHUB_TOKEN}}
default-branch: main
token: ${{secrets.RELEASE_PLEASE_ACTION_TOKEN}}
outputs:
release_created: ${{ fromJSON(steps.release.outputs.paths_released)[0] != null }} # if we have a single release path, do the release
# These steps are only run if this was a merged release-please PR
- name: checkout
if: ${{ steps.release.outputs.release_created }}
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- name: Set up JDK 8
if: ${{ steps.release.outputs.release_created }}
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
publish:
environment: publish
runs-on: ubuntu-latest
permissions:
contents: read
needs: release-please
if: ${{ fromJSON(needs.release-please.outputs.release_created || false) }}
steps:
- name: Checkout Repository
uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
- name: Set up JDK 17
uses: actions/setup-java@ae2b61dbc685e60e4427b2e8ed4f0135c6ea8597
with:
java-version: '8'
java-version: '17'
distribution: 'temurin'
cache: maven
server-id: ossrh
server-username: ${{ secrets.OSSRH_USERNAME }}
server-password: ${{ secrets.OSSRH_PASSWORD }}
server-id: central
server-username: ${{ secrets.CENTRAL_USERNAME }}
server-password: ${{ secrets.CENTRAL_PASSWORD }}
- name: Configure GPG Key
if: ${{ steps.release.outputs.release_created }}
run: |
echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --import
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
- name: Deploy
if: ${{ steps.release.outputs.release_created }}
run: |
mvn --batch-mode \
--settings release/m2-settings.xml clean deploy
--settings release/m2-settings.xml -DskipTests clean deploy
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}

View File

@ -29,16 +29,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@1c15a48f3fb49ce535e9ee4e57e127315f669361
uses: github/codeql-action/init@7710ed11e398ea99c7f7004c2b2e0f580458db42
with:
languages: java
- name: Autobuild
uses: github/codeql-action/autobuild@1c15a48f3fb49ce535e9ee4e57e127315f669361
uses: github/codeql-action/autobuild@7710ed11e398ea99c7f7004c2b2e0f580458db42
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@1c15a48f3fb49ce535e9ee4e57e127315f669361
uses: github/codeql-action/analyze@7710ed11e398ea99c7f7004c2b2e0f580458db42

View File

@ -16,4 +16,4 @@
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip

View File

@ -1 +1 @@
{".":"1.14.1"}
{".":"1.16.0"}

View File

@ -1,5 +1,211 @@
# Changelog
## [1.16.0](https://github.com/open-feature/java-sdk/compare/v1.15.1...v1.16.0) (2025-07-07)
### 🐛 Bug Fixes
* **deps:** update dependency io.cucumber:cucumber-bom to v7.23.0 ([#1466](https://github.com/open-feature/java-sdk/issues/1466)) ([50a6b16](https://github.com/open-feature/java-sdk/commit/50a6b168a7de40337aa51ef3d79d122030956cb9))
* **deps:** update dependency org.junit:junit-bom to v5.13.1 ([#1475](https://github.com/open-feature/java-sdk/issues/1475)) ([545d6aa](https://github.com/open-feature/java-sdk/commit/545d6aac09dbc74c00a0a4e5c26f4ef80be22379))
* **deps:** update dependency org.junit:junit-bom to v5.13.2 ([#1492](https://github.com/open-feature/java-sdk/issues/1492)) ([34b22e8](https://github.com/open-feature/java-sdk/commit/34b22e8d93a986fdb81500ab539b4d2fe038b618))
* **deps:** update dependency org.junit:junit-bom to v5.13.3 ([#1505](https://github.com/open-feature/java-sdk/issues/1505)) ([957c0d1](https://github.com/open-feature/java-sdk/commit/957c0d1ba38ecc758c1ec164e40070ac93a01d68))
* **deps:** update junit5 monorepo ([#1467](https://github.com/open-feature/java-sdk/issues/1467)) ([f8260a1](https://github.com/open-feature/java-sdk/commit/f8260a1c3a345c877eba95bfe41184ad11f6555e))
* Reduce locking and concurrency issues ([#1478](https://github.com/open-feature/java-sdk/issues/1478)) ([ebea0fd](https://github.com/open-feature/java-sdk/commit/ebea0fdf1cf3e6f4d2e8aebf2dcb7c7e1f31acc2))
### ✨ New Features
* add means of awaiting event emission, fix flaky build ([#1463](https://github.com/open-feature/java-sdk/issues/1463)) ([3dd7d5d](https://github.com/open-feature/java-sdk/commit/3dd7d5d4262f1f4461e13c13a7d64d2fa8bfd764)), closes [#1449](https://github.com/open-feature/java-sdk/issues/1449)
### 🧹 Chore
* **deps:** update actions/cache digest to 640a1c2 ([#1485](https://github.com/open-feature/java-sdk/issues/1485)) ([7c2af57](https://github.com/open-feature/java-sdk/commit/7c2af57a362ee11f757a431ee17eff3ee448bf6c))
* **deps:** update actions/checkout digest to 09d2aca ([#1473](https://github.com/open-feature/java-sdk/issues/1473)) ([b5d873e](https://github.com/open-feature/java-sdk/commit/b5d873e44d3c41b42f11569b0fafccc0a002ebdd))
* **deps:** update actions/setup-java digest to 67aec00 ([#1504](https://github.com/open-feature/java-sdk/issues/1504)) ([08f549a](https://github.com/open-feature/java-sdk/commit/08f549afd1fd26581b2a8e063832ec986c5e3267))
* **deps:** update actions/setup-java digest to ebb356c ([#1490](https://github.com/open-feature/java-sdk/issues/1490)) ([e67f598](https://github.com/open-feature/java-sdk/commit/e67f5983573afff805a56ef18584d1a7291ccafc))
* **deps:** update codecov/codecov-action action to v5.4.3 ([#1454](https://github.com/open-feature/java-sdk/issues/1454)) ([e337939](https://github.com/open-feature/java-sdk/commit/e3379395e6bfb0ce811d8372761a3cb015ad2cde))
* **deps:** update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.5 ([#1462](https://github.com/open-feature/java-sdk/issues/1462)) ([40b319c](https://github.com/open-feature/java-sdk/commit/40b319c5de0461bec13f76978ae09edc958310cd))
* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.3.1 ([#1493](https://github.com/open-feature/java-sdk/issues/1493)) ([b64efe8](https://github.com/open-feature/java-sdk/commit/b64efe82d993defe070dfeb9aa60e740ccf757cd))
* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.3.2 ([#1496](https://github.com/open-feature/java-sdk/issues/1496)) ([fc430c3](https://github.com/open-feature/java-sdk/commit/fc430c3e1d57a532d8c0c879c3e7e25c46d4ad84))
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.24.0 ([#1458](https://github.com/open-feature/java-sdk/issues/1458)) ([dcbfd26](https://github.com/open-feature/java-sdk/commit/dcbfd265a3875271695af760fce9870e53c69f13))
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.25.0 ([#1468](https://github.com/open-feature/java-sdk/issues/1468)) ([1558a86](https://github.com/open-feature/java-sdk/commit/1558a862497c0e133d11d53ff6d7f28437653d43))
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.25.1 ([#1489](https://github.com/open-feature/java-sdk/issues/1489)) ([312b6df](https://github.com/open-feature/java-sdk/commit/312b6df5d2c891ac758bf398f8399ecd25b7597e))
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.26.0 ([#1494](https://github.com/open-feature/java-sdk/issues/1494)) ([300a705](https://github.com/open-feature/java-sdk/commit/300a705e0af959da7ed0e88e9975379ff6fc4138))
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.26.1 ([#1498](https://github.com/open-feature/java-sdk/issues/1498)) ([2e3b479](https://github.com/open-feature/java-sdk/commit/2e3b479cb1e8b0b65652ee813eaa2e1940d53c8e))
* **deps:** update dependency maven to v3.9.10 ([#1474](https://github.com/open-feature/java-sdk/issues/1474)) ([4481537](https://github.com/open-feature/java-sdk/commit/4481537cebc213dcfe19bb8cd9b70a4c91a682b2))
* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.6 ([#1482](https://github.com/open-feature/java-sdk/issues/1482)) ([8e51e6f](https://github.com/open-feature/java-sdk/commit/8e51e6fe101882184a5d09be31fa65563d82c673))
* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.6 ([#1483](https://github.com/open-feature/java-sdk/issues/1483)) ([936ff60](https://github.com/open-feature/java-sdk/commit/936ff60fac471a83a7c14412d2e825b2a7f9704c))
* **deps:** update dependency org.apache.maven.plugins:maven-gpg-plugin to v3.2.8 ([#1501](https://github.com/open-feature/java-sdk/issues/1501)) ([0515ad5](https://github.com/open-feature/java-sdk/commit/0515ad54c4f71863373eb1b7f429393923b27d90))
* **deps:** update dependency org.codehaus.mojo:exec-maven-plugin to v3.5.1 ([#1461](https://github.com/open-feature/java-sdk/issues/1461)) ([b6ceff2](https://github.com/open-feature/java-sdk/commit/b6ceff2ecb0e34be2ccdb83f7f37c1177de6f27e))
* **deps:** update dependency org.mockito:mockito-core to v5.18.0 ([#1457](https://github.com/open-feature/java-sdk/issues/1457)) ([e17b0b2](https://github.com/open-feature/java-sdk/commit/e17b0b29758ae7cdbdac9ddb2178382c55eb1277))
* **deps:** update github/codeql-action digest to 075e08a ([#1470](https://github.com/open-feature/java-sdk/issues/1470)) ([6597de7](https://github.com/open-feature/java-sdk/commit/6597de7a98e0fae10a541a8a9b60837623c133a8))
* **deps:** update github/codeql-action digest to 33f8489 ([#1502](https://github.com/open-feature/java-sdk/issues/1502)) ([0fd9d3d](https://github.com/open-feature/java-sdk/commit/0fd9d3dcfb1fd65197a42885b12d40a1cc152d3b))
* **deps:** update github/codeql-action digest to 396fd27 ([#1456](https://github.com/open-feature/java-sdk/issues/1456)) ([b45a937](https://github.com/open-feature/java-sdk/commit/b45a9370173e3d3b97c78449dfc99225fb572228))
* **deps:** update github/codeql-action digest to 3de706a ([#1481](https://github.com/open-feature/java-sdk/issues/1481)) ([99a3006](https://github.com/open-feature/java-sdk/commit/99a3006de878ab0ba1f0e61a4cb5432914425795))
* **deps:** update github/codeql-action digest to 466d6ce ([#1477](https://github.com/open-feature/java-sdk/issues/1477)) ([0b57bca](https://github.com/open-feature/java-sdk/commit/0b57bcafc14b946000feb4a3421d73b9616e83cb))
* **deps:** update github/codeql-action digest to 4a00331 ([#1469](https://github.com/open-feature/java-sdk/issues/1469)) ([376f81f](https://github.com/open-feature/java-sdk/commit/376f81f5c3b66d7e3e298aac30ac7544b84e7362))
* **deps:** update github/codeql-action digest to 4c57370 ([#1497](https://github.com/open-feature/java-sdk/issues/1497)) ([49214b7](https://github.com/open-feature/java-sdk/commit/49214b7282ddde1ee16cf80f92c11cc90ef7612a))
* **deps:** update github/codeql-action digest to 510dfa3 ([#1450](https://github.com/open-feature/java-sdk/issues/1450)) ([d9a72d2](https://github.com/open-feature/java-sdk/commit/d9a72d2aafd787a1814132f000897ad1c94181e4))
* **deps:** update github/codeql-action digest to 57eebf6 ([#1455](https://github.com/open-feature/java-sdk/issues/1455)) ([36eed06](https://github.com/open-feature/java-sdk/commit/36eed065e763bbfa0f8f97d704202bbd219332ca))
* **deps:** update github/codeql-action digest to 66d7255 ([#1487](https://github.com/open-feature/java-sdk/issues/1487)) ([c3eaecd](https://github.com/open-feature/java-sdk/commit/c3eaecdb8b34d3b33946bd205ee92d49584602bd))
* **deps:** update github/codeql-action digest to 7b0fb5a ([#1459](https://github.com/open-feature/java-sdk/issues/1459)) ([6a95c00](https://github.com/open-feature/java-sdk/commit/6a95c008e975dd3c7328c32f1d7cf626bbaecfa6))
* **deps:** update github/codeql-action digest to 7cb9b16 ([#1476](https://github.com/open-feature/java-sdk/issues/1476)) ([6cca721](https://github.com/open-feature/java-sdk/commit/6cca721be5bc6f5926fe64668a7c03728cab3cb0))
* **deps:** update github/codeql-action digest to 7fd6215 ([#1464](https://github.com/open-feature/java-sdk/issues/1464)) ([f10aaaa](https://github.com/open-feature/java-sdk/commit/f10aaaa357581b573895f4d6e2329abb705582aa))
* **deps:** update github/codeql-action digest to 8ef1782 ([#1495](https://github.com/open-feature/java-sdk/issues/1495)) ([86a5916](https://github.com/open-feature/java-sdk/commit/86a5916f0dc6116b5b9e5dc897ff4b8705ac01e3))
* **deps:** update github/codeql-action digest to 9b02dc2 ([#1491](https://github.com/open-feature/java-sdk/issues/1491)) ([6f67b06](https://github.com/open-feature/java-sdk/commit/6f67b06f712c461f331681a76f5cb2c3ddb0d36b))
* **deps:** update github/codeql-action digest to ac30a39 ([#1488](https://github.com/open-feature/java-sdk/issues/1488)) ([8fad544](https://github.com/open-feature/java-sdk/commit/8fad544b17ee08b4280d7975073d00a874c374db))
* **deps:** update github/codeql-action digest to b1e4dc3 ([#1471](https://github.com/open-feature/java-sdk/issues/1471)) ([2dcd6a1](https://github.com/open-feature/java-sdk/commit/2dcd6a1dd0c80ee676b9860afd6a6002d0ea4aea))
* **deps:** update github/codeql-action digest to b694213 ([#1503](https://github.com/open-feature/java-sdk/issues/1503)) ([a5d1cbc](https://github.com/open-feature/java-sdk/commit/a5d1cbced4658fadb63f362b4512bdbd68ae7d6a))
* **deps:** update github/codeql-action digest to b86edfc ([#1453](https://github.com/open-feature/java-sdk/issues/1453)) ([b667aa3](https://github.com/open-feature/java-sdk/commit/b667aa325136b78c01867d40342f81eeb7e16f46))
* **deps:** update github/codeql-action digest to bc02a25 ([#1460](https://github.com/open-feature/java-sdk/issues/1460)) ([5e922cf](https://github.com/open-feature/java-sdk/commit/5e922cf3efc156135563707de92e508b0a4d19f3))
* **deps:** update github/codeql-action digest to be30325 ([#1479](https://github.com/open-feature/java-sdk/issues/1479)) ([844d5e2](https://github.com/open-feature/java-sdk/commit/844d5e244b02703b624cf75e5bf8448c07e62d3d))
* **deps:** update github/codeql-action digest to dcc1a66 ([#1499](https://github.com/open-feature/java-sdk/issues/1499)) ([69519b1](https://github.com/open-feature/java-sdk/commit/69519b1ef7274ceae39d6746c5a5a98dc69f562f))
* **deps:** update github/codeql-action digest to ef36b69 ([#1484](https://github.com/open-feature/java-sdk/issues/1484)) ([8bf777a](https://github.com/open-feature/java-sdk/commit/8bf777a7e99be4dfac8917b8e61cb6c23385b8ce))
* **deps:** update io.cucumber.version to v7.23.0 ([#1465](https://github.com/open-feature/java-sdk/issues/1465)) ([2de7616](https://github.com/open-feature/java-sdk/commit/2de76166764bacd34883b13220dd0bad824c8b1a))
* improvements to release workflow ([#1451](https://github.com/open-feature/java-sdk/issues/1451)) ([1714efe](https://github.com/open-feature/java-sdk/commit/1714efe81aa6ae025f4f8b12c9c042561498d25e))
* migrate to new publish ([5425a34](https://github.com/open-feature/java-sdk/commit/5425a34a12baa04f9583b83fd1bfdd7e2a6ab5e8))
* remove unneeded version information ([#1428](https://github.com/open-feature/java-sdk/issues/1428)) ([3ed65cf](https://github.com/open-feature/java-sdk/commit/3ed65cfb0cb5ee5b70793cd68a27909c81cd4fab))
* skip tests on publish ([6194186](https://github.com/open-feature/java-sdk/commit/6194186b3e791f3cb28da24f5acb3ff96788d65e))
* update publish env vars ([85d89ee](https://github.com/open-feature/java-sdk/commit/85d89ee79a52d960322731fb786c0f60245f0d75))
## [1.15.1](https://github.com/open-feature/java-sdk/compare/v1.14.2...v1.15.1) (2025-05-14)
### NOTABLE CHANGES
* Raise required Java version to 11 ([#1393](https://github.com/open-feature/java-sdk/issues/1393))
### 🐛 Bug Fixes
* **deps:** update dependency io.cucumber:cucumber-bom to v7.22.0 ([#1411](https://github.com/open-feature/java-sdk/issues/1411)) ([e251819](https://github.com/open-feature/java-sdk/commit/e25181982af8e5d37be4876b71b337ca86e8454b))
* **deps:** update dependency io.cucumber:cucumber-bom to v7.22.1 ([#1427](https://github.com/open-feature/java-sdk/issues/1427)) ([1c4d2ef](https://github.com/open-feature/java-sdk/commit/1c4d2efafdebb562f099ba1ec3a6a29eabc8ff91))
* **deps:** update dependency io.cucumber:cucumber-bom to v7.22.2 ([#1442](https://github.com/open-feature/java-sdk/issues/1442)) ([e568f3a](https://github.com/open-feature/java-sdk/commit/e568f3a4f560187586d5473aa7bc12a673340e24))
* **deps:** update dependency org.projectlombok:lombok to v1.18.38 ([#1403](https://github.com/open-feature/java-sdk/issues/1403)) ([ef32f11](https://github.com/open-feature/java-sdk/commit/ef32f11571de4d3a981efec4f61113eb8b0d7d9d))
* **deps:** update junit5 monorepo ([#1418](https://github.com/open-feature/java-sdk/issues/1418)) ([97b442e](https://github.com/open-feature/java-sdk/commit/97b442ed6e8f2b99ca949ffd63e5cbf57718c796))
### ✨ New Features
* add logging on provider state transitions ([#1444](https://github.com/open-feature/java-sdk/issues/1444)) ([e2813b2](https://github.com/open-feature/java-sdk/commit/e2813b2e5df8e548caf16e3e425b35962045ca6c))
* add telemetry helper utils ([#1346](https://github.com/open-feature/java-sdk/issues/1346)) ([d0ae548](https://github.com/open-feature/java-sdk/commit/d0ae5482771f4d1701bce25381cdf4e92e2d4882))
* Raise required Java version to 11 ([#1393](https://github.com/open-feature/java-sdk/issues/1393)) ([4dc988b](https://github.com/open-feature/java-sdk/commit/4dc988b637a9e9c377edf7df7b29bf6407319f16))
### 🧹 Chore
* add DCO to release please ([45ec4b1](https://github.com/open-feature/java-sdk/commit/45ec4b1b7734c9117f43abf8fe5105c2903c3986))
* add DCO to release please ([#1429](https://github.com/open-feature/java-sdk/issues/1429)) ([32137bf](https://github.com/open-feature/java-sdk/commit/32137bfa82e9c0391c999bf0be2a36f201620931))
* add publish env ([#1420](https://github.com/open-feature/java-sdk/issues/1420)) ([665dd51](https://github.com/open-feature/java-sdk/commit/665dd51eb2b3b79d3ffccb6cef64d544aa5e7206))
* **deps:** update actions/setup-java digest to 148017a ([#1404](https://github.com/open-feature/java-sdk/issues/1404)) ([f834e11](https://github.com/open-feature/java-sdk/commit/f834e11acc7ecf903e972d80e9dab324be97847e))
* **deps:** update actions/setup-java digest to c5195ef ([#1415](https://github.com/open-feature/java-sdk/issues/1415)) ([a578903](https://github.com/open-feature/java-sdk/commit/a5789038acc36cb2b0ddf12e534a1317e1c9b8e8))
* **deps:** update actions/setup-java digest to f4f1212 ([#1421](https://github.com/open-feature/java-sdk/issues/1421)) ([a3e2a59](https://github.com/open-feature/java-sdk/commit/a3e2a59aebee051ae8c7eb1c5769a04dc9da8de3))
* **deps:** update amannn/action-semantic-pull-request digest to 3352882 ([#1434](https://github.com/open-feature/java-sdk/issues/1434)) ([62ba6db](https://github.com/open-feature/java-sdk/commit/62ba6db457358d759fe83f23318b1cf4200756ac))
* **deps:** update codecov/codecov-action action to v5.4.2 ([#1419](https://github.com/open-feature/java-sdk/issues/1419)) ([a6389e8](https://github.com/open-feature/java-sdk/commit/a6389e89f60aa7f4871f47d78fedd27a7f9991b4))
* **deps:** update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.4 ([#1414](https://github.com/open-feature/java-sdk/issues/1414)) ([e066d3f](https://github.com/open-feature/java-sdk/commit/e066d3f749c09bb1ef79e3bcace1d205a39787df))
* **deps:** update dependency com.h3xstream.findsecbugs:findsecbugs-plugin to v1.14.0 ([#1422](https://github.com/open-feature/java-sdk/issues/1422)) ([495da27](https://github.com/open-feature/java-sdk/commit/495da271bee976a942973cd23012f60db895bf24))
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10 ([#103](https://github.com/open-feature/java-sdk/issues/103)) ([3403510](https://github.com/open-feature/java-sdk/commit/34035105154b7945c02de2a88fe83eb2414526ef))
* **deps:** update dependency com.tngtech.archunit:archunit-junit5 to v1.4.1 ([#1440](https://github.com/open-feature/java-sdk/issues/1440)) ([78657ee](https://github.com/open-feature/java-sdk/commit/78657ee79efdc94018387cdf8263a73d4abf7191))
* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.5 ([#1400](https://github.com/open-feature/java-sdk/issues/1400)) ([1f2d071](https://github.com/open-feature/java-sdk/commit/1f2d0715087ebd4554826d8552b250e4b8b950c8))
* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.5 ([#1401](https://github.com/open-feature/java-sdk/issues/1401)) ([07301bd](https://github.com/open-feature/java-sdk/commit/07301bda3f5b65550eff1e025fc9c0bec3c25275))
* **deps:** update dependency org.apache.maven.plugins:maven-failsafe-plugin to v3.5.3 ([#1398](https://github.com/open-feature/java-sdk/issues/1398)) ([1fcf0e7](https://github.com/open-feature/java-sdk/commit/1fcf0e77d956c88c54e10942d96d2afd4d79315c))
* **deps:** update dependency org.apache.maven.plugins:maven-surefire-plugin to v3.5.3 ([#1399](https://github.com/open-feature/java-sdk/issues/1399)) ([d6ebc16](https://github.com/open-feature/java-sdk/commit/d6ebc161a93ad703e25592abdb0bf0fd9e281bbc))
* **deps:** update dependency org.jacoco:jacoco-maven-plugin to v0.8.13 ([#1407](https://github.com/open-feature/java-sdk/issues/1407)) ([e19ccaa](https://github.com/open-feature/java-sdk/commit/e19ccaa35d9ac4d89d72ea58a70d416d202078db))
* **deps:** update dependency org.mockito:mockito-core to v5.17.0 ([#1409](https://github.com/open-feature/java-sdk/issues/1409)) ([345cdcf](https://github.com/open-feature/java-sdk/commit/345cdcfa10da64c61d769746f335f38ac564e9ad))
* **deps:** update github/codeql-action digest to 15bce5b ([#1443](https://github.com/open-feature/java-sdk/issues/1443)) ([bc10bac](https://github.com/open-feature/java-sdk/commit/bc10bacb5a68d0d2e498cb41c087505490f19de8))
* **deps:** update github/codeql-action digest to 2a8cbad ([#1423](https://github.com/open-feature/java-sdk/issues/1423)) ([6b6849f](https://github.com/open-feature/java-sdk/commit/6b6849f3a3ee8a7b66d859c8e522bc101d1ccd44))
* **deps:** update github/codeql-action digest to 362ef4c ([#1408](https://github.com/open-feature/java-sdk/issues/1408)) ([ca160ca](https://github.com/open-feature/java-sdk/commit/ca160cab7ccd71527e06a0851502353ac50b8d0d))
* **deps:** update github/codeql-action digest to 40e16ed ([#1437](https://github.com/open-feature/java-sdk/issues/1437)) ([f965cbc](https://github.com/open-feature/java-sdk/commit/f965cbcb37d20724e15b76c15842a88574810b1a))
* **deps:** update github/codeql-action digest to 4c3e536 ([#1417](https://github.com/open-feature/java-sdk/issues/1417)) ([0c77c84](https://github.com/open-feature/java-sdk/commit/0c77c8446032eaac7e068d48901e1423c21db326))
* **deps:** update github/codeql-action digest to 4ffa236 ([#1425](https://github.com/open-feature/java-sdk/issues/1425)) ([a7828e7](https://github.com/open-feature/java-sdk/commit/a7828e73a8f2e30f71bd2d9d4da180b2fa436424))
* **deps:** update github/codeql-action digest to 56dd02f ([#1416](https://github.com/open-feature/java-sdk/issues/1416)) ([4607c62](https://github.com/open-feature/java-sdk/commit/4607c62f15f7ee572207b8ec012ad4b3626e0184))
* **deps:** update github/codeql-action digest to 5eb3ed6 ([#1439](https://github.com/open-feature/java-sdk/issues/1439)) ([f2348ea](https://github.com/open-feature/java-sdk/commit/f2348ea370412351389c60eef390f36edbea68b0))
* **deps:** update github/codeql-action digest to 83605b3 ([#1435](https://github.com/open-feature/java-sdk/issues/1435)) ([7e74f2a](https://github.com/open-feature/java-sdk/commit/7e74f2aa3ad2dc8f7a3e4ad398e7705b3e3db364))
* **deps:** update github/codeql-action digest to 97a2bfd ([#1438](https://github.com/open-feature/java-sdk/issues/1438)) ([85b200a](https://github.com/open-feature/java-sdk/commit/85b200a08b9f8a71de3b5a19eaa057ec04e0801e))
* **deps:** update github/codeql-action digest to 9f45e74 ([#1396](https://github.com/open-feature/java-sdk/issues/1396)) ([37d76be](https://github.com/open-feature/java-sdk/commit/37d76be697e83f524250a82b2a67cdb4a953d7bc))
* **deps:** update github/codeql-action digest to d26c46a ([#1413](https://github.com/open-feature/java-sdk/issues/1413)) ([5b327ee](https://github.com/open-feature/java-sdk/commit/5b327eeb770d0a4222f3599be79543b7bed9abc2))
* **deps:** update github/codeql-action digest to dab8a02 ([#1405](https://github.com/open-feature/java-sdk/issues/1405)) ([5b2f151](https://github.com/open-feature/java-sdk/commit/5b2f1513ab75ef6692978830e59eba87ffa494d5))
* **deps:** update github/codeql-action digest to e13fe0d ([#1406](https://github.com/open-feature/java-sdk/issues/1406)) ([e211397](https://github.com/open-feature/java-sdk/commit/e211397d517e1263e1251f9c99093bf05cecd93f))
* **deps:** update github/codeql-action digest to ed51cb5 ([#1436](https://github.com/open-feature/java-sdk/issues/1436)) ([b09e887](https://github.com/open-feature/java-sdk/commit/b09e88798fed529161c61b96c20a8f257d355d3c))
* **deps:** update github/codeql-action digest to efffb48 ([#1402](https://github.com/open-feature/java-sdk/issues/1402)) ([384953d](https://github.com/open-feature/java-sdk/commit/384953d30ecff83d60a2e5b9790e8228d1a52ac7))
* **deps:** update github/codeql-action digest to f843d94 ([#1432](https://github.com/open-feature/java-sdk/issues/1432)) ([99faaf8](https://github.com/open-feature/java-sdk/commit/99faaf88aa07bd45fc473db5bafce3b8eafaf9e0))
* **deps:** update io.cucumber.version to v7.22.0 ([#1410](https://github.com/open-feature/java-sdk/issues/1410)) ([3c69f2f](https://github.com/open-feature/java-sdk/commit/3c69f2f36c4e975d690ecc2e790df632a33001ba))
* **deps:** update io.cucumber.version to v7.22.1 ([#1426](https://github.com/open-feature/java-sdk/issues/1426)) ([844374a](https://github.com/open-feature/java-sdk/commit/844374a42b94deffab6856e978766354a6f46576))
* **deps:** update io.cucumber.version to v7.22.2 ([#1441](https://github.com/open-feature/java-sdk/issues/1441)) ([58454b4](https://github.com/open-feature/java-sdk/commit/58454b4eaabfd3327f7ceaff4bf335a5a839ed41))
* **main:** release 1.15.0 ([#1431](https://github.com/open-feature/java-sdk/issues/1431)) ([7182a7f](https://github.com/open-feature/java-sdk/commit/7182a7fc4197e70218e829971dae2cff09f948c9))
* update boostrap sha for release please ([f6bd30d](https://github.com/open-feature/java-sdk/commit/f6bd30db93e37e596d211d899315a62d9f810199))
* update codeowners to give global maintainers code ownership ([#1412](https://github.com/open-feature/java-sdk/issues/1412)) ([498fd38](https://github.com/open-feature/java-sdk/commit/498fd382659669315b0db61db5f19ce054467bc9))
* update release please action ([#1430](https://github.com/open-feature/java-sdk/issues/1430)) ([1cc851b](https://github.com/open-feature/java-sdk/commit/1cc851b293008a8dd273e904e4c77a650ad71146))
* use PAT for release please ([014f8a5](https://github.com/open-feature/java-sdk/commit/014f8a59da8f1e976e440ed1ea17e85561f98e2d))
### 📚 Documentation
* add try-catch example for setProviderAndWait usage ([#1433](https://github.com/open-feature/java-sdk/issues/1433)) ([96cf9c7](https://github.com/open-feature/java-sdk/commit/96cf9c7f5463e4e0de394117845aebdd9a69425f))
## [1.14.2](https://github.com/open-feature/java-sdk/compare/v1.14.1...v1.14.2) (2025-03-27)
### 🐛 Bug Fixes
* **deps:** update dependency org.slf4j:slf4j-api to v2.0.17 ([#1348](https://github.com/open-feature/java-sdk/issues/1348)) ([2ec7c6c](https://github.com/open-feature/java-sdk/commit/2ec7c6c7ff704380fdfd8116378adf78734e4f2b))
* **deps:** update junit5 monorepo ([#1344](https://github.com/open-feature/java-sdk/issues/1344)) ([d95e270](https://github.com/open-feature/java-sdk/commit/d95e2706532259bd5739e5b4ea4813ef9f2196a6))
* **deps:** update junit5 monorepo ([#1373](https://github.com/open-feature/java-sdk/issues/1373)) ([6b65e26](https://github.com/open-feature/java-sdk/commit/6b65e26c7439895652c3f64f2b4a7307a7ca582e))
* equals and hashcode of several classes ([69b571e](https://github.com/open-feature/java-sdk/commit/69b571eda73b6f43c99864420b8663ae54ebf0ad))
* equals and hashcode of several classes ([#1364](https://github.com/open-feature/java-sdk/issues/1364)) ([69b571e](https://github.com/open-feature/java-sdk/commit/69b571eda73b6f43c99864420b8663ae54ebf0ad))
* hooks not run in NOT_READY/FATAL ([#1392](https://github.com/open-feature/java-sdk/issues/1392)) ([24ef9dd](https://github.com/open-feature/java-sdk/commit/24ef9dd2903d01ec029b70cd1e39e71ffe327499))
### 🧹 Chore
* **deps:** update actions/cache digest to 5a3ec84 ([#1380](https://github.com/open-feature/java-sdk/issues/1380)) ([8359ef1](https://github.com/open-feature/java-sdk/commit/8359ef13bb935ac1d144787cfd7181814a0b286c))
* **deps:** update actions/cache digest to 7921ae2 ([#1337](https://github.com/open-feature/java-sdk/issues/1337)) ([3920c63](https://github.com/open-feature/java-sdk/commit/3920c638a49caddfb07041f812cc6bc0bf3101f9))
* **deps:** update actions/cache digest to d4323d4 ([#1353](https://github.com/open-feature/java-sdk/issues/1353)) ([5901797](https://github.com/open-feature/java-sdk/commit/59017977a487a36c8a39f63b83299bc657134c0d))
* **deps:** update actions/setup-java digest to 3b6c050 ([#1391](https://github.com/open-feature/java-sdk/issues/1391)) ([7536679](https://github.com/open-feature/java-sdk/commit/753667925a8803b3b227f762936ae397dde95484))
* **deps:** update actions/setup-java digest to 799ee7c ([#1359](https://github.com/open-feature/java-sdk/issues/1359)) ([31444d6](https://github.com/open-feature/java-sdk/commit/31444d6c8f30f0dd35debacc9dab8da7397e11ed))
* **deps:** update actions/setup-java digest to b8ebb8b ([#1381](https://github.com/open-feature/java-sdk/issues/1381)) ([2239f05](https://github.com/open-feature/java-sdk/commit/2239f054b90734dde6cdd4a23daec1c1daa96f07))
* **deps:** update amannn/action-semantic-pull-request digest to 04501d4 ([#1390](https://github.com/open-feature/java-sdk/issues/1390)) ([87c06d9](https://github.com/open-feature/java-sdk/commit/87c06d9edd935287daf7ebc8db1e7da4831531de))
* **deps:** update codecov/codecov-action action to v5.4.0 ([#1351](https://github.com/open-feature/java-sdk/issues/1351)) ([b133c2f](https://github.com/open-feature/java-sdk/commit/b133c2fa527a0dddb6de7f7781a00fc84feaa813))
* **deps:** update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.3 ([#1341](https://github.com/open-feature/java-sdk/issues/1341)) ([5de33c0](https://github.com/open-feature/java-sdk/commit/5de33c02a675db6ca5966bfa3f58d99c8e53e36b))
* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.1.0 ([#1332](https://github.com/open-feature/java-sdk/issues/1332)) ([cdcdc14](https://github.com/open-feature/java-sdk/commit/cdcdc143ea5ad2f003cb3f5450ec78314e619ea3))
* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.2.0 ([#1360](https://github.com/open-feature/java-sdk/issues/1360)) ([ecea9df](https://github.com/open-feature/java-sdk/commit/ecea9df932ee4874613f219b73640fe964c99593))
* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.3.0 ([#1375](https://github.com/open-feature/java-sdk/issues/1375)) ([de3e213](https://github.com/open-feature/java-sdk/commit/de3e213ac8b8931121904a3d12929405512e74dd))
* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.2 ([#1355](https://github.com/open-feature/java-sdk/issues/1355)) ([2a1adca](https://github.com/open-feature/java-sdk/commit/2a1adca8c2ed8d61d51530969290793a5d3d15f3))
* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.3 ([#1384](https://github.com/open-feature/java-sdk/issues/1384)) ([b6becac](https://github.com/open-feature/java-sdk/commit/b6becac2c4e0f98a8651cc2f77d4c0b081548991))
* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.4 ([#1387](https://github.com/open-feature/java-sdk/issues/1387)) ([cb574d9](https://github.com/open-feature/java-sdk/commit/cb574d93b6210c89a188aa104ef4f1db68daf1c0))
* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.2 ([#1356](https://github.com/open-feature/java-sdk/issues/1356)) ([dd83114](https://github.com/open-feature/java-sdk/commit/dd83114c4d9389753575392fafcd56585d7178ae))
* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.3 ([#1385](https://github.com/open-feature/java-sdk/issues/1385)) ([4125ae8](https://github.com/open-feature/java-sdk/commit/4125ae83801a9f485059a9edaca090ee47b7632f))
* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.4 ([#1388](https://github.com/open-feature/java-sdk/issues/1388)) ([d8f6514](https://github.com/open-feature/java-sdk/commit/d8f6514598d53f43cb084ee746742a59d271363b))
* **deps:** update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.14.0 ([#1342](https://github.com/open-feature/java-sdk/issues/1342)) ([88a778c](https://github.com/open-feature/java-sdk/commit/88a778cc03e112d45756428d1f0ae1ef0fe02c84))
* **deps:** update dependency org.awaitility:awaitility to v4.3.0 ([#1343](https://github.com/open-feature/java-sdk/issues/1343)) ([1504d0f](https://github.com/open-feature/java-sdk/commit/1504d0f7982757a2b413eda593ce7057b90519e5))
* **deps:** update dependency org.mockito:mockito-core to v5.15.2 ([#1339](https://github.com/open-feature/java-sdk/issues/1339)) ([4817864](https://github.com/open-feature/java-sdk/commit/4817864fd7ae70c1e19c3c09e82e1fb03dd88942))
* **deps:** update dependency org.mockito:mockito-core to v5.16.0 ([#1358](https://github.com/open-feature/java-sdk/issues/1358)) ([30b6d00](https://github.com/open-feature/java-sdk/commit/30b6d004aaf3464547805f7eda6fad0e122de4f9))
* **deps:** update dependency org.mockito:mockito-core to v5.16.1 ([#1376](https://github.com/open-feature/java-sdk/issues/1376)) ([9750f75](https://github.com/open-feature/java-sdk/commit/9750f75d04beb8339fc2e972f0ee97120eaff354))
* **deps:** update github/codeql-action digest to 1bb15d0 ([#1336](https://github.com/open-feature/java-sdk/issues/1336)) ([e163ce1](https://github.com/open-feature/java-sdk/commit/e163ce1c060d0dc8812e4a8a3b37f52b0156324d))
* **deps:** update github/codeql-action digest to 486ab5a ([#1389](https://github.com/open-feature/java-sdk/issues/1389)) ([85fd5e0](https://github.com/open-feature/java-sdk/commit/85fd5e0997ff1a5e5d7226d8bbfe2775769a6ca6))
* **deps:** update github/codeql-action digest to 56b25d5 ([#1365](https://github.com/open-feature/java-sdk/issues/1365)) ([959e675](https://github.com/open-feature/java-sdk/commit/959e675e4c2363e5fd80d1d2f1edbfab11794fc8))
* **deps:** update github/codeql-action digest to 608ccd6 ([#1361](https://github.com/open-feature/java-sdk/issues/1361)) ([67b34f8](https://github.com/open-feature/java-sdk/commit/67b34f84a373512013ab2f7649faaddfd2d61048))
* **deps:** update github/codeql-action digest to 6349095 ([#1378](https://github.com/open-feature/java-sdk/issues/1378)) ([dbf92df](https://github.com/open-feature/java-sdk/commit/dbf92df33bf5657d50dc3b2f129207b0097c1f27))
* **deps:** update github/codeql-action digest to 6a151cd ([#1377](https://github.com/open-feature/java-sdk/issues/1377)) ([7065655](https://github.com/open-feature/java-sdk/commit/706565581d78856dd73605b1a16b131f974c0731))
* **deps:** update github/codeql-action digest to 70df9de ([#1372](https://github.com/open-feature/java-sdk/issues/1372)) ([d233480](https://github.com/open-feature/java-sdk/commit/d233480912f1d5e095f5034f36a838535d1ecdff))
* **deps:** update github/codeql-action digest to 7254660 ([#1368](https://github.com/open-feature/java-sdk/issues/1368)) ([d54c68a](https://github.com/open-feature/java-sdk/commit/d54c68a8e9e4a0f67c99e7d76621a1c5724e4cd1))
* **deps:** update github/codeql-action digest to 80f9930 ([#1357](https://github.com/open-feature/java-sdk/issues/1357)) ([6c03e5d](https://github.com/open-feature/java-sdk/commit/6c03e5d84aacee11f5b8e608a6114c11fced72b8))
* **deps:** update github/codeql-action digest to 8392354 ([#1352](https://github.com/open-feature/java-sdk/issues/1352)) ([989f4ae](https://github.com/open-feature/java-sdk/commit/989f4ae54263b46ca2c81561acc70b39918c382d))
* **deps:** update github/codeql-action digest to 8c1551c ([#1333](https://github.com/open-feature/java-sdk/issues/1333)) ([859a36c](https://github.com/open-feature/java-sdk/commit/859a36cbfafc94d4601b87d304237e6ddf97c08d))
* **deps:** update github/codeql-action digest to 8c69433 ([#1347](https://github.com/open-feature/java-sdk/issues/1347)) ([6987568](https://github.com/open-feature/java-sdk/commit/698756856ba40e98d91ccf661dab409798861aa5))
* **deps:** update github/codeql-action digest to 97aac9b ([#1350](https://github.com/open-feature/java-sdk/issues/1350)) ([7df9565](https://github.com/open-feature/java-sdk/commit/7df9565691731d164b534116b8a6b933b171d103))
* **deps:** update github/codeql-action digest to a8849fb ([#1345](https://github.com/open-feature/java-sdk/issues/1345)) ([de64edd](https://github.com/open-feature/java-sdk/commit/de64eddfb3a6cc117bb108dbcf167830e9f6729d))
* **deps:** update github/codeql-action digest to acadfed ([#1335](https://github.com/open-feature/java-sdk/issues/1335)) ([5436eb0](https://github.com/open-feature/java-sdk/commit/5436eb0d5db3a0e9bd9289fbef57b9eeada0a667))
* **deps:** update github/codeql-action digest to b2e6519 ([#1366](https://github.com/open-feature/java-sdk/issues/1366)) ([d00e4b5](https://github.com/open-feature/java-sdk/commit/d00e4b5b24621aa55085827fbe6ea982491376de))
* **deps:** update github/codeql-action digest to b46b37a ([#1367](https://github.com/open-feature/java-sdk/issues/1367)) ([c550d59](https://github.com/open-feature/java-sdk/commit/c550d597227bfc1e0e17357139f1fd8a87593be0))
* **deps:** update github/codeql-action digest to bd1d9ab ([#1383](https://github.com/open-feature/java-sdk/issues/1383)) ([922e17e](https://github.com/open-feature/java-sdk/commit/922e17e677e15690e3df2fe93a961f16f21ff283))
* **deps:** update github/codeql-action digest to c50c157 ([#1379](https://github.com/open-feature/java-sdk/issues/1379)) ([d61c33e](https://github.com/open-feature/java-sdk/commit/d61c33e466336c7120b870ca5e3843eba5f7175c))
* **deps:** update github/codeql-action digest to d99c7e8 ([#1338](https://github.com/open-feature/java-sdk/issues/1338)) ([4e535fd](https://github.com/open-feature/java-sdk/commit/4e535fd10fac742ca472faa62c941fa51b282ca7))
* **deps:** update github/codeql-action digest to dc49dca ([#1369](https://github.com/open-feature/java-sdk/issues/1369)) ([f8df5fb](https://github.com/open-feature/java-sdk/commit/f8df5fb84a765af917587dd509f9cec38103f787))
* **deps:** update github/codeql-action digest to e0ea141 ([#1386](https://github.com/open-feature/java-sdk/issues/1386)) ([387e5f2](https://github.com/open-feature/java-sdk/commit/387e5f2e3bd24ccea6691b0d6dbfe542cfd05b52))
* **deps:** update github/codeql-action digest to ff79de6 ([#1340](https://github.com/open-feature/java-sdk/issues/1340)) ([50b45b2](https://github.com/open-feature/java-sdk/commit/50b45b2be442bb89a431c9bcc45d825f63bd93a6))
* update build and tooling to utilize new java version ([#1321](https://github.com/open-feature/java-sdk/issues/1321)) ([90217b2](https://github.com/open-feature/java-sdk/commit/90217b2083a2ba92c623365dc450326d49b46fab))
## [1.14.1](https://github.com/open-feature/java-sdk/compare/v1.14.0...v1.14.1) (2025-02-14)

View File

@ -3,4 +3,4 @@
#
# Managed by Peribolos: https://github.com/open-feature/community/blob/main/config/open-feature/sdk-java/workgroup.yaml
#
* @open-feature/sdk-java-maintainers
* @open-feature/sdk-java-maintainers @open-feature/maintainers

View File

@ -18,8 +18,8 @@
</a>
<!-- x-release-please-start-version -->
<a href="https://github.com/open-feature/java-sdk/releases/tag/v1.14.1">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.14.1&color=blue&style=for-the-badge" />
<a href="https://github.com/open-feature/java-sdk/releases/tag/v1.16.0">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.16.0&color=blue&style=for-the-badge" />
</a>
<!-- x-release-please-end -->
@ -46,7 +46,7 @@
### Requirements
- Java 8+ (compiler target is 1.8)
- Java 11+ (compiler target is 11)
Note that this library is intended to be used in server-side contexts and has not been evaluated for use on mobile devices.
@ -59,7 +59,7 @@ Note that this library is intended to be used in server-side contexts and has no
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>1.14.1</version>
<version>1.16.0</version>
</dependency>
```
<!-- x-release-please-end-version -->
@ -84,7 +84,7 @@ If you would like snapshot builds, this is the relevant repository information:
<!-- x-release-please-start-version -->
```groovy
dependencies {
implementation 'dev.openfeature:sdk:1.14.1'
implementation 'dev.openfeature:sdk:1.16.0'
}
```
<!-- x-release-please-end-version -->
@ -104,7 +104,12 @@ public void example(){
// configure a provider
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new InMemoryProvider(myFlags));
try {
api.setProviderAndWait(new InMemoryProvider(myFlags));
} catch (Exception e) {
// handle initialization failure
e.printStackTrace();
}
// create a client
Client client = api.getClient();
@ -149,7 +154,12 @@ To register a provider in a blocking manner to ensure it is ready before further
```java
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new MyProvider());
try {
api.setProviderAndWait(new MyProvider());
} catch (Exception e) {
// handle initialization failure
e.printStackTrace();
}
```
#### Asynchronous

578
pom.xml
View File

@ -5,16 +5,20 @@
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>1.14.1</version> <!--x-release-please-version -->
<version>1.16.0</version> <!--x-release-please-version -->
<properties>
<toolchain.jdk.version>[17,)</toolchain.jdk.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
<junit.jupiter.version>5.11.4</junit.jupiter.version>
<org.mockito.version>5.18.0</org.mockito.version>
<!-- exclusion expression for e2e tests -->
<testExclusions>**/e2e/*.java</testExclusions>
<module-name>${project.groupId}.${project.artifactId}</module-name>
<skip.tests>false</skip.tests>
<!-- this will throw an error if we use wrong apis -->
<maven.compiler.release>11</maven.compiler.release>
</properties>
<name>OpenFeature Java SDK</name>
@ -48,7 +52,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<version>1.18.38</version>
<scope>provided</scope>
</dependency>
@ -63,14 +67,21 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
<version>2.0.17</version>
</dependency>
<!-- test -->
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.4.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<version>${org.mockito.version}</version>
<scope>test</scope>
</dependency>
@ -84,35 +95,30 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.11.4</version>
<scope>test</scope>
</dependency>
@ -128,6 +134,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.simplify4u</groupId>
<artifactId>slf4j2-mock</artifactId>
@ -138,14 +150,14 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.0-jre</version>
<version>33.4.8-jre</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.2.2</version>
<version>4.3.0</version>
<scope>test</scope>
</dependency>
@ -167,14 +179,14 @@
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.17.1</version>
<version>1.17.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.17.1</version>
<version>1.17.6</version>
<scope>test</scope>
</dependency>
<!-- End mockito workaround-->
@ -182,7 +194,7 @@
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-bom</artifactId>
<version>7.21.1</version>
<version>7.26.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -190,7 +202,7 @@
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.11.4</version>
<version>5.13.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -200,6 +212,18 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-toolchains-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals>
<goal>select-jdk-toolchain</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
@ -226,49 +250,22 @@
</executions>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.8.1</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>analyze</goal>
</goals>
</execution>
</executions>
<configuration>
<failOnWarning>true</failOnWarning>
<ignoredUnusedDeclaredDependencies>
<ignoredUnusedDeclaredDependency>com.github.spotbugs:*</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>org.junit*</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>org.simplify4u:slf4j2-mock*</ignoredUnusedDeclaredDependency>
</ignoredUnusedDeclaredDependencies>
<ignoredDependencies>
<ignoredDependency>com.google.guava*</ignoredDependency>
<ignoredDependency>io.cucumber*</ignoredDependency>
<ignoredDependency>org.junit*</ignoredDependency>
<ignoredDependency>com.google.code.findbugs*</ignoredDependency>
<ignoredDependency>com.github.spotbugs*</ignoredDependency>
<ignoredDependency>org.simplify4u:slf4j-mock-common:*</ignoredDependency>
</ignoredDependencies>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<version>3.14.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
<version>3.5.3</version>
<configuration>
<forkCount>1</forkCount>
<reuseForks>false</reuseForks>
<argLine>
${surefireArgLine}
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
<excludes>
<!-- tests to exclude -->
@ -280,7 +277,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.2</version>
<version>3.5.3</version>
<configuration>
<argLine>
${surefireArgLine}
@ -288,65 +285,6 @@
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
<propertyName>surefireArgLine</propertyName>
</configuration>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
<outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
</configuration>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
<excludes>
<exclude>dev/openfeature/sdk/exceptions/**</exclude>
</excludes>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -361,134 +299,217 @@
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.26.0</version>
<executions>
<execution>
<id>run-pmd</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.6.6</version>
<configuration>
<excludeFilterFile>spotbugs-exclusions.xml</excludeFilterFile>
<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>1.13.0</version>
</plugin>
</plugins>
</configuration>
<dependencies>
<!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>4.8.6</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>run-spotbugs</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
</configuration>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>9.3</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.30.0</version>
<configuration>
<!-- optional: limit format enforcement to just the files changed by this feature branch -->
<!-- <ratchetFrom>origin/main</ratchetFrom>-->
<formats>
<!-- you can define as many formats as you want, each is independent -->
<format>
<!-- define the files to apply to -->
<includes>
<include>.gitattributes</include>
<include>.gitignore</include>
</includes>
<!-- define the steps to apply to those files -->
<trimTrailingWhitespace/>
<endWithNewline/>
<indent>
<spaces>true</spaces>
<spacesPerTab>4</spacesPerTab>
</indent>
</format>
</formats>
<!-- define a language-specific format -->
<java>
<palantirJavaFormat/>
<indent>
<spaces>true</spaces>
<spacesPerTab>4</spacesPerTab>
</indent>
<importOrder/>
<removeUnusedImports/>
<formatAnnotations/>
</java>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- Deploy related plugins are isolated so that local development can ignore them -->
<profiles>
<profile>
<id>codequality</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.8.1</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>analyze</goal>
</goals>
</execution>
</executions>
<configuration>
<failOnWarning>true</failOnWarning>
<ignoredUnusedDeclaredDependencies>
<ignoredUnusedDeclaredDependency>com.github.spotbugs:*</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>org.junit*</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>com.tngtech.archunit*</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>org.simplify4u:slf4j2-mock*</ignoredUnusedDeclaredDependency>
</ignoredUnusedDeclaredDependencies>
<ignoredDependencies>
<ignoredDependency>com.google.guava*</ignoredDependency>
<ignoredDependency>io.cucumber*</ignoredDependency>
<ignoredDependency>org.junit*</ignoredDependency>
<ignoredDependency>com.tngtech.archunit*</ignoredDependency>
<ignoredDependency>com.google.code.findbugs*</ignoredDependency>
<ignoredDependency>com.github.spotbugs*</ignoredDependency>
<ignoredDependency>org.simplify4u:slf4j-mock-common:*</ignoredDependency>
</ignoredDependencies>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.13</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
<propertyName>surefireArgLine</propertyName>
</configuration>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
<outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
</configuration>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
<excludes>
<exclude>dev/openfeature/sdk/exceptions/**</exclude>
</excludes>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.9.3.2</version>
<configuration>
<excludeFilterFile>spotbugs-exclusions.xml</excludeFilterFile>
<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>1.14.0</version>
</plugin>
</plugins>
</configuration>
<dependencies>
<!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>4.8.6</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>run-spotbugs</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
</configuration>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.26.1</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.45.0</version>
<configuration>
<!-- optional: limit format enforcement to just the files changed by this feature branch -->
<!-- <ratchetFrom>origin/main</ratchetFrom>-->
<formats>
<!-- you can define as many formats as you want, each is independent -->
<format>
<!-- define the files to apply to -->
<includes>
<include>.gitattributes</include>
<include>.gitignore</include>
</includes>
<!-- define the steps to apply to those files -->
<trimTrailingWhitespace/>
<endWithNewline/>
<indent>
<spaces>true</spaces>
<spacesPerTab>4</spacesPerTab>
</indent>
</format>
</formats>
<!-- define a language-specific format -->
<java>
<palantirJavaFormat/>
<indent>
<spaces>true</spaces>
<spacesPerTab>4</spacesPerTab>
</indent>
<importOrder/>
<removeUnusedImports/>
<formatAnnotations/>
</java>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>deploy</id>
<activation>
@ -499,14 +520,13 @@
<plugins>
<!-- Begin publish to maven central -->
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.7.0</version>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.8.0</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
</configuration>
</plugin>
<!-- End publish to maven central -->
@ -550,7 +570,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.7</version>
<version>3.2.8</version>
<executions>
<execution>
<id>sign-artifacts</id>
@ -591,7 +611,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<version>3.5.1</version>
<executions>
<execution>
<id>update-test-harness-submodule</id>
@ -610,19 +630,75 @@
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- profile for running tests under java 11 (used mostly in CI) -->
<!-- selected automatically by JDK activation (see https://maven.apache.org/guides/introduction/introduction-to-profiles.html#implicit-profile-activation) -->
<profile>
<id>java11</id>
<!-- with the next block we can define a set of sdks which still support java 8, if any of the sdks is not supporting java 8 anymore -->
<!--<modules><module></module></modules>-->
<properties>
<toolchain.jdk.version>[11,)</toolchain.jdk.version>
<skip.tests>true</skip.tests>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-toolchains-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>copy-evaluation-gherkin-tests</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
<goal>select-jdk-toolchain</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.3</version>
<configuration>
<argLine>
${surefireArgLine}
</argLine>
<excludes>
<!-- tests to exclude -->
<exclude>${testExclusions}</exclude>
</excludes>
<skipTests>${skip.tests}</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.3</version>
<configuration>
<argLine>
${surefireArgLine}
</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<executions>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<!-- copy the feature spec we want to test into resources so them can be easily loaded -->
<executable>cp</executable>
<arguments>
<argument>spec/specification/assets/gherkin/evaluation.feature</argument>
<argument>src/test/resources/features/</argument>
</arguments>
<skip>true</skip>
</configuration>
</execution>
</executions>
@ -634,8 +710,8 @@
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
<id>central</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>

View File

@ -1,5 +1,6 @@
{
"bootstrap-sha": "c701a6c4ebbe1170a25ca7636a31508b9628831c",
"bootstrap-sha": "d7b591c9f910afad303d6d814f65c7f9dab33b89",
"signoff": "OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>",
"packages": {
".": {
"package-name": "dev.openfeature.sdk",

View File

@ -5,5 +5,10 @@
<username>${env.OSSRH_USERNAME}</username>
<password>${env.OSSRH_PASSWORD}</password>
</server>
<server>
<id>central</id>
<username>${env.CENTRAL_USERNAME}</username>
<password>${env.CENTRAL_PASSWORD}</password>
</server>
</servers>
</settings>

View File

@ -3,15 +3,17 @@ package dev.openfeature.sdk;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import lombok.EqualsAndHashCode;
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
@EqualsAndHashCode
abstract class AbstractStructure implements Structure {
protected final Map<String, Value> attributes;
@Override
public boolean isEmpty() {
return attributes == null || attributes.size() == 0;
return attributes == null || attributes.isEmpty();
}
AbstractStructure() {

View File

@ -0,0 +1,44 @@
package dev.openfeature.sdk;
/**
* A class to help with synchronization by allowing the optional awaiting of the associated action.
*/
public class Awaitable {
/**
* An already-completed Awaitable. Awaiting this will return immediately.
*/
public static final Awaitable FINISHED = new Awaitable(true);
private boolean isDone = false;
public Awaitable() {}
private Awaitable(boolean isDone) {
this.isDone = isDone;
}
/**
* Lets the calling thread wait until some other thread calls {@link Awaitable#wakeup()}. If
* {@link Awaitable#wakeup()} has been called before the current thread invokes this method, it will return
* immediately.
*/
@SuppressWarnings("java:S2142")
public synchronized void await() {
while (!isDone) {
try {
this.wait();
} catch (InterruptedException ignored) {
// ignored, do not propagate the interrupted state
}
}
}
/**
* Wakes up all threads that have called {@link Awaitable#await()} and lets them proceed.
*/
public synchronized void wakeup() {
isDone = true;
this.notifyAll();
}
}

View File

@ -0,0 +1,24 @@
package dev.openfeature.sdk;
import java.util.HashMap;
import java.util.Map;
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
/**
* Represents an evaluation event.
*/
@Builder
@Getter
public class EvaluationEvent {
private String name;
@Singular("attribute")
private Map<String, Object> attributes;
public Map<String, Object> getAttributes() {
return new HashMap<>(attributes);
}
}

View File

@ -1,11 +1,13 @@
package dev.openfeature.sdk;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder;
/**
* The details of a particular event.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@SuperBuilder(toBuilder = true)
public class EventDetails extends ProviderEventDetails {

View File

@ -76,15 +76,32 @@ public abstract class EventProvider implements FeatureProvider {
* @param event The event type
* @param details The details of the event
*/
public void emit(ProviderEvent event, ProviderEventDetails details) {
if (eventProviderListener != null) {
eventProviderListener.onEmit(event, details);
public Awaitable emit(final ProviderEvent event, final ProviderEventDetails details) {
final var localEventProviderListener = this.eventProviderListener;
final var localOnEmit = this.onEmit;
if (localEventProviderListener == null && localOnEmit == null) {
return Awaitable.FINISHED;
}
final TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> localOnEmit = this.onEmit;
if (localOnEmit != null) {
emitterExecutor.submit(() -> localOnEmit.accept(this, event, details));
}
final var awaitable = new Awaitable();
// These calls need to be executed on a different thread to prevent deadlocks when the provider initialization
// relies on a ready event to be emitted
emitterExecutor.submit(() -> {
try (var ignored = OpenFeatureAPI.lock.readLockAutoCloseable()) {
if (localEventProviderListener != null) {
localEventProviderListener.onEmit(event, details);
}
if (localOnEmit != null) {
localOnEmit.accept(this, event, details);
}
} finally {
awaitable.wakeup();
}
});
return awaitable;
}
/**
@ -93,8 +110,8 @@ public abstract class EventProvider implements FeatureProvider {
*
* @param details The details of the event
*/
public void emitProviderReady(ProviderEventDetails details) {
emit(ProviderEvent.PROVIDER_READY, details);
public Awaitable emitProviderReady(ProviderEventDetails details) {
return emit(ProviderEvent.PROVIDER_READY, details);
}
/**
@ -104,8 +121,8 @@ public abstract class EventProvider implements FeatureProvider {
*
* @param details The details of the event
*/
public void emitProviderConfigurationChanged(ProviderEventDetails details) {
emit(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, details);
public Awaitable emitProviderConfigurationChanged(ProviderEventDetails details) {
return emit(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, details);
}
/**
@ -114,8 +131,8 @@ public abstract class EventProvider implements FeatureProvider {
*
* @param details The details of the event
*/
public void emitProviderStale(ProviderEventDetails details) {
emit(ProviderEvent.PROVIDER_STALE, details);
public Awaitable emitProviderStale(ProviderEventDetails details) {
return emit(ProviderEvent.PROVIDER_STALE, details);
}
/**
@ -124,7 +141,7 @@ public abstract class EventProvider implements FeatureProvider {
*
* @param details The details of the event
*/
public void emitProviderError(ProviderEventDetails details) {
emit(ProviderEvent.PROVIDER_ERROR, details);
public Awaitable emitProviderError(ProviderEventDetails details) {
return emit(ProviderEvent.PROVIDER_ERROR, details);
}
}

View File

@ -1,12 +1,12 @@
package dev.openfeature.sdk;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@ -23,13 +23,10 @@ class EventSupport {
// we use a v4 uuid as a "placeholder" for anonymous clients, since
// ConcurrentHashMap doesn't support nulls
private static final String defaultClientUuid = UUID.randomUUID().toString();
private static final String DEFAULT_CLIENT_UUID = UUID.randomUUID().toString();
private final Map<String, HandlerStore> handlerStores = new ConcurrentHashMap<>();
private final HandlerStore globalHandlerStore = new HandlerStore();
private final ExecutorService taskExecutor = Executors.newCachedThreadPool(runnable -> {
final Thread thread = new Thread(runnable);
return thread;
});
private final ExecutorService taskExecutor = Executors.newCachedThreadPool();
/**
* Run all the event handlers associated with this domain.
@ -40,11 +37,10 @@ class EventSupport {
* @param eventDetails the event details
*/
public void runClientHandlers(String domain, ProviderEvent event, EventDetails eventDetails) {
domain = Optional.ofNullable(domain).orElse(defaultClientUuid);
domain = Optional.ofNullable(domain).orElse(DEFAULT_CLIENT_UUID);
// run handlers if they exist
Optional.ofNullable(handlerStores.get(domain))
.filter(store -> Optional.of(store).isPresent())
.map(store -> store.handlerMap.get(event))
.ifPresent(handlers -> handlers.forEach(handler -> runHandler(handler, eventDetails)));
}
@ -69,7 +65,7 @@ class EventSupport {
* @param handler the handler function to run
*/
public void addClientHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
final String name = Optional.ofNullable(domain).orElse(defaultClientUuid);
final String name = Optional.ofNullable(domain).orElse(DEFAULT_CLIENT_UUID);
// lazily create and cache a HandlerStore if it doesn't exist
HandlerStore store = Optional.ofNullable(this.handlerStores.get(name)).orElseGet(() -> {
@ -89,7 +85,7 @@ class EventSupport {
* @param handler the handler ref to be removed
*/
public void removeClientHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
domain = Optional.ofNullable(domain).orElse(defaultClientUuid);
domain = Optional.ofNullable(domain).orElse(DEFAULT_CLIENT_UUID);
this.handlerStores.get(domain).removeHandler(event, handler);
}
@ -160,14 +156,14 @@ class EventSupport {
// instantiated when a handler is added to that client.
static class HandlerStore {
private final Map<ProviderEvent, List<Consumer<EventDetails>>> handlerMap;
private final Map<ProviderEvent, Collection<Consumer<EventDetails>>> handlerMap;
HandlerStore() {
handlerMap = new ConcurrentHashMap<>();
handlerMap.put(ProviderEvent.PROVIDER_READY, new ArrayList<>());
handlerMap.put(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, new ArrayList<>());
handlerMap.put(ProviderEvent.PROVIDER_ERROR, new ArrayList<>());
handlerMap.put(ProviderEvent.PROVIDER_STALE, new ArrayList<>());
handlerMap.put(ProviderEvent.PROVIDER_READY, new ConcurrentLinkedQueue<>());
handlerMap.put(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, new ConcurrentLinkedQueue<>());
handlerMap.put(ProviderEvent.PROVIDER_ERROR, new ConcurrentLinkedQueue<>());
handlerMap.put(ProviderEvent.PROVIDER_STALE, new ConcurrentLinkedQueue<>());
}
void addHandler(ProviderEvent event, Consumer<EventDetails> handler) {

View File

@ -30,6 +30,7 @@ public interface FeatureProvider {
* can overwrite this method,
* if they have special initialization needed prior being called for flag
* evaluation.
*
* <p>
* It is ok if the method is expensive as it is executed in the background. All
* runtime exceptions will be
@ -45,6 +46,7 @@ public interface FeatureProvider {
* flags, or the SDK is shut down.
* Providers can overwrite this method, if they have special shutdown actions
* needed.
*
* <p>
* It is ok if the method is expensive as it is executed in the background. All
* runtime exceptions will be

View File

@ -2,14 +2,14 @@ package dev.openfeature.sdk;
import dev.openfeature.sdk.exceptions.OpenFeatureError;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.Getter;
import java.util.concurrent.atomic.AtomicReference;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class FeatureProviderStateManager implements EventProviderListener {
private final FeatureProvider delegate;
private final AtomicBoolean isInitialized = new AtomicBoolean();
@Getter
private ProviderState state = ProviderState.NOT_READY;
private final AtomicReference<ProviderState> state = new AtomicReference<>(ProviderState.NOT_READY);
public FeatureProviderStateManager(FeatureProvider delegate) {
this.delegate = delegate;
@ -24,17 +24,17 @@ class FeatureProviderStateManager implements EventProviderListener {
}
try {
delegate.initialize(evaluationContext);
state = ProviderState.READY;
setState(ProviderState.READY);
} catch (OpenFeatureError openFeatureError) {
if (ErrorCode.PROVIDER_FATAL.equals(openFeatureError.getErrorCode())) {
state = ProviderState.FATAL;
setState(ProviderState.FATAL);
} else {
state = ProviderState.ERROR;
setState(ProviderState.ERROR);
}
isInitialized.set(false);
throw openFeatureError;
} catch (Exception e) {
state = ProviderState.ERROR;
setState(ProviderState.ERROR);
isInitialized.set(false);
throw e;
}
@ -42,7 +42,7 @@ class FeatureProviderStateManager implements EventProviderListener {
public void shutdown() {
delegate.shutdown();
state = ProviderState.NOT_READY;
setState(ProviderState.NOT_READY);
isInitialized.set(false);
}
@ -50,17 +50,34 @@ class FeatureProviderStateManager implements EventProviderListener {
public void onEmit(ProviderEvent event, ProviderEventDetails details) {
if (ProviderEvent.PROVIDER_ERROR.equals(event)) {
if (details != null && details.getErrorCode() == ErrorCode.PROVIDER_FATAL) {
state = ProviderState.FATAL;
setState(ProviderState.FATAL);
} else {
state = ProviderState.ERROR;
setState(ProviderState.ERROR);
}
} else if (ProviderEvent.PROVIDER_STALE.equals(event)) {
state = ProviderState.STALE;
setState(ProviderState.STALE);
} else if (ProviderEvent.PROVIDER_READY.equals(event)) {
state = ProviderState.READY;
setState(ProviderState.READY);
}
}
private void setState(ProviderState state) {
ProviderState oldState = this.state.getAndSet(state);
if (oldState != state) {
String providerName;
if (delegate.getMetadata() == null || delegate.getMetadata().getName() == null) {
providerName = "unknown";
} else {
providerName = delegate.getMetadata().getName();
}
log.info("Provider {} transitioned from state {} to state {}", providerName, oldState, state);
}
}
public ProviderState getState() {
return state.get();
}
FeatureProvider getProvider() {
return delegate;
}

View File

@ -4,6 +4,7 @@ import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Delegate;
@ -15,6 +16,7 @@ import lombok.experimental.Delegate;
* not be modified after instantiation.
*/
@ToString
@EqualsAndHashCode
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
public final class ImmutableContext implements EvaluationContext {

View File

@ -97,6 +97,14 @@ public class ImmutableMetadata {
}
}
public boolean isEmpty() {
return metadata.isEmpty();
}
public boolean isNotEmpty() {
return !metadata.isEmpty();
}
/**
* Obtain a builder for {@link ImmutableMetadata}.
*/

View File

@ -18,7 +18,7 @@ import lombok.ToString;
* not be modified after instantiation. All references are clones.
*/
@ToString
@EqualsAndHashCode
@EqualsAndHashCode(callSuper = true)
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
public final class ImmutableStructure extends AbstractStructure {
@ -38,7 +38,7 @@ public final class ImmutableStructure extends AbstractStructure {
super(copyAttributes(attributes, null));
}
protected ImmutableStructure(String targetingKey, Map<String, Value> attributes) {
ImmutableStructure(String targetingKey, Map<String, Value> attributes) {
super(copyAttributes(attributes, targetingKey));
}
@ -70,12 +70,14 @@ public final class ImmutableStructure extends AbstractStructure {
private static Map<String, Value> copyAttributes(Map<String, Value> in, String targetingKey) {
Map<String, Value> copy = new HashMap<>();
for (Entry<String, Value> entry : in.entrySet()) {
copy.put(
entry.getKey(),
Optional.ofNullable(entry.getValue())
.map((Value val) -> val.clone())
.orElse(null));
if (in != null) {
for (Entry<String, Value> entry : in.entrySet()) {
copy.put(
entry.getKey(),
Optional.ofNullable(entry.getValue())
.map((Value val) -> val.clone())
.orElse(null));
}
}
if (targetingKey != null) {
copy.put(EvaluationContext.TARGETING_KEY, new Value(targetingKey));

View File

@ -15,8 +15,8 @@ import lombok.ToString;
* be modified after instantiation.
*/
@ToString
@EqualsAndHashCode
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
@EqualsAndHashCode(callSuper = true)
public class MutableStructure extends AbstractStructure {
public MutableStructure() {

View File

@ -5,9 +5,12 @@ import dev.openfeature.sdk.internal.AutoCloseableLock;
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
@ -21,15 +24,15 @@ import lombok.extern.slf4j.Slf4j;
public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
// package-private multi-read/single-write lock
static AutoCloseableReentrantReadWriteLock lock = new AutoCloseableReentrantReadWriteLock();
private final List<Hook> apiHooks;
private final ConcurrentLinkedQueue<Hook> apiHooks;
private ProviderRepository providerRepository;
private EventSupport eventSupport;
private EvaluationContext evaluationContext;
private final AtomicReference<EvaluationContext> evaluationContext = new AtomicReference<>();
private TransactionContextPropagator transactionContextPropagator;
protected OpenFeatureAPI() {
apiHooks = new ArrayList<>();
providerRepository = new ProviderRepository();
apiHooks = new ConcurrentLinkedQueue<>();
providerRepository = new ProviderRepository(this);
eventSupport = new EventSupport();
transactionContextPropagator = new NoOpTransactionContextPropagator();
}
@ -115,9 +118,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* @return api instance
*/
public OpenFeatureAPI setEvaluationContext(EvaluationContext evaluationContext) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
this.evaluationContext = evaluationContext;
}
this.evaluationContext.set(evaluationContext);
return this;
}
@ -127,16 +128,14 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* @return evaluation context
*/
public EvaluationContext getEvaluationContext() {
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
return this.evaluationContext;
}
return evaluationContext.get();
}
/**
* Return the transaction context propagator.
*/
public TransactionContextPropagator getTransactionContextPropagator() {
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.readLockAutoCloseable()) {
return this.transactionContextPropagator;
}
}
@ -150,7 +149,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
if (transactionContextPropagator == null) {
throw new IllegalArgumentException("Transaction context propagator cannot be null");
}
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
this.transactionContextPropagator = transactionContextPropagator;
}
}
@ -176,7 +175,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* Set the default provider.
*/
public void setProvider(FeatureProvider provider) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
providerRepository.setProvider(
provider,
this::attachEventProvider,
@ -194,7 +193,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* @param provider The provider to set.
*/
public void setProvider(String domain, FeatureProvider provider) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
providerRepository.setProvider(
domain,
provider,
@ -207,10 +206,16 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
}
/**
* Set the default provider and wait for initialization to finish.
* Sets the default provider and waits for its initialization to complete.
*
* <p>Note: If the provider fails during initialization, an {@link OpenFeatureError} will be thrown.
* It is recommended to wrap this call in a try-catch block to handle potential initialization failures gracefully.
*
* @param provider the {@link FeatureProvider} to set as the default.
* @throws OpenFeatureError if the provider fails during initialization.
*/
public void setProviderAndWait(FeatureProvider provider) throws OpenFeatureError {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
providerRepository.setProvider(
provider,
this::attachEventProvider,
@ -224,11 +229,15 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
/**
* Add a provider for a domain and wait for initialization to finish.
*
* <p>Note: If the provider fails during initialization, an {@link OpenFeatureError} will be thrown.
* It is recommended to wrap this call in a try-catch block to handle potential initialization failures gracefully.
*
* @param domain The domain to bind the provider to.
* @param provider The provider to set.
* @throws OpenFeatureError if the provider fails during initialization.
*/
public void setProviderAndWait(String domain, FeatureProvider provider) throws OpenFeatureError {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
providerRepository.setProvider(
domain,
provider,
@ -242,9 +251,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
private void attachEventProvider(FeatureProvider provider) {
if (provider instanceof EventProvider) {
((EventProvider) provider).attach((p, event, details) -> {
runHandlersForProvider(p, event, details);
});
((EventProvider) provider).attach(this::runHandlersForProvider);
}
}
@ -297,9 +304,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* @param hooks The hook to add.
*/
public void addHooks(Hook... hooks) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
this.apiHooks.addAll(Arrays.asList(hooks));
}
this.apiHooks.addAll(Arrays.asList(hooks));
}
/**
@ -308,18 +313,23 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* @return A list of {@link Hook}s.
*/
public List<Hook> getHooks() {
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
return this.apiHooks;
}
return new ArrayList<>(this.apiHooks);
}
/**
* Returns a reference to the collection of {@link Hook}s.
*
* @return The collection of {@link Hook}s.
*/
Collection<Hook> getMutableHooks() {
return this.apiHooks;
}
/**
* Removes all hooks.
*/
public void clearHooks() {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
this.apiHooks.clear();
}
this.apiHooks.clear();
}
/**
@ -329,11 +339,11 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* Once shut down is complete, API is reset and ready to use again.
*/
public void shutdown() {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
providerRepository.shutdown();
eventSupport.shutdown();
providerRepository = new ProviderRepository();
providerRepository = new ProviderRepository(this);
eventSupport = new EventSupport();
}
}
@ -375,7 +385,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
*/
@Override
public OpenFeatureAPI on(ProviderEvent event, Consumer<EventDetails> handler) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
this.eventSupport.addGlobalHandler(event, handler);
return this;
}
@ -386,18 +396,20 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
*/
@Override
public OpenFeatureAPI removeHandler(ProviderEvent event, Consumer<EventDetails> handler) {
this.eventSupport.removeGlobalHandler(event, handler);
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
this.eventSupport.removeGlobalHandler(event, handler);
}
return this;
}
void removeHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
eventSupport.removeClientHandler(domain, event, handler);
}
}
void addHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
// if the provider is in the state associated with event, run immediately
if (Optional.ofNullable(this.providerRepository.getProviderState(domain))
.orElse(ProviderState.READY)
@ -421,32 +433,28 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* @param details the event details
*/
private void runHandlersForProvider(FeatureProvider provider, ProviderEvent event, ProviderEventDetails details) {
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
try (AutoCloseableLock ignored = lock.readLockAutoCloseable()) {
List<String> domainsForProvider = providerRepository.getDomainsForProvider(provider);
final String providerName = Optional.ofNullable(provider.getMetadata())
.map(metadata -> metadata.getName())
.map(Metadata::getName)
.orElse(null);
// run the global handlers
eventSupport.runGlobalHandlers(event, EventDetails.fromProviderEventDetails(details, providerName));
// run the handlers associated with domains for this provider
domainsForProvider.forEach(domain -> {
eventSupport.runClientHandlers(
domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain));
});
domainsForProvider.forEach(domain -> eventSupport.runClientHandlers(
domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain)));
if (providerRepository.isDefaultProvider(provider)) {
// run handlers for clients that have no bound providers (since this is the default)
Set<String> allDomainNames = eventSupport.getAllDomainNames();
Set<String> boundDomains = providerRepository.getAllBoundDomains();
allDomainNames.removeAll(boundDomains);
allDomainNames.forEach(domain -> {
eventSupport.runClientHandlers(
domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain));
});
allDomainNames.forEach(domain -> eventSupport.runClientHandlers(
domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain)));
}
}
}

View File

@ -5,9 +5,8 @@ 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.AutoCloseableLock;
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
import dev.openfeature.sdk.internal.ObjectUtils;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -15,6 +14,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@ -46,11 +47,9 @@ public class OpenFeatureClient implements Client {
@Getter
private final String version;
private final List<Hook> clientHooks;
private final ConcurrentLinkedQueue<Hook> clientHooks;
private final HookSupport hookSupport;
AutoCloseableReentrantReadWriteLock hooksLock = new AutoCloseableReentrantReadWriteLock();
AutoCloseableReentrantReadWriteLock contextLock = new AutoCloseableReentrantReadWriteLock();
private EvaluationContext evaluationContext;
private final AtomicReference<EvaluationContext> evaluationContext = new AtomicReference<>();
/**
* Deprecated public constructor. Use OpenFeature.API.getClient() instead.
@ -68,7 +67,7 @@ public class OpenFeatureClient implements Client {
this.openfeatureApi = openFeatureAPI;
this.domain = domain;
this.version = version;
this.clientHooks = new ArrayList<>();
this.clientHooks = new ConcurrentLinkedQueue<>();
this.hookSupport = new HookSupport();
}
@ -125,9 +124,7 @@ public class OpenFeatureClient implements Client {
*/
@Override
public OpenFeatureClient addHooks(Hook... hooks) {
try (AutoCloseableLock __ = this.hooksLock.writeLockAutoCloseable()) {
this.clientHooks.addAll(Arrays.asList(hooks));
}
this.clientHooks.addAll(Arrays.asList(hooks));
return this;
}
@ -136,9 +133,7 @@ public class OpenFeatureClient implements Client {
*/
@Override
public List<Hook> getHooks() {
try (AutoCloseableLock __ = this.hooksLock.readLockAutoCloseable()) {
return this.clientHooks;
}
return new ArrayList<>(this.clientHooks);
}
/**
@ -146,9 +141,7 @@ public class OpenFeatureClient implements Client {
*/
@Override
public OpenFeatureClient setEvaluationContext(EvaluationContext evaluationContext) {
try (AutoCloseableLock __ = contextLock.writeLockAutoCloseable()) {
this.evaluationContext = evaluationContext;
}
this.evaluationContext.set(evaluationContext);
return this;
}
@ -157,38 +150,33 @@ public class OpenFeatureClient implements Client {
*/
@Override
public EvaluationContext getEvaluationContext() {
try (AutoCloseableLock __ = contextLock.readLockAutoCloseable()) {
return this.evaluationContext;
}
return this.evaluationContext.get();
}
@SuppressFBWarnings(
value = {"REC_CATCH_EXCEPTION"},
justification = "We don't want to allow any exception to reach the user. "
+ "Instead, we return an evaluation result with the appropriate error code.")
private <T> FlagEvaluationDetails<T> evaluateFlag(
FlagValueType type, String key, T defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(
var flagOptions = ObjectUtils.defaultIfNull(
options, () -> FlagEvaluationOptions.builder().build());
Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
var hints = Collections.unmodifiableMap(flagOptions.getHookHints());
FlagEvaluationDetails<T> details = null;
List<Hook> mergedHooks = null;
HookContext<T> afterHookContext = null;
FeatureProvider provider;
try {
FeatureProviderStateManager stateManager = openfeatureApi.getFeatureProviderStateManager(this.domain);
var stateManager = openfeatureApi.getFeatureProviderStateManager(this.domain);
// provider must be accessed once to maintain a consistent reference
provider = stateManager.getProvider();
ProviderState state = stateManager.getState();
if (ProviderState.NOT_READY.equals(state)) {
throw new ProviderNotReadyError("provider not yet initialized");
}
if (ProviderState.FATAL.equals(state)) {
throw new FatalError("provider is in an irrecoverable error state");
}
var provider = stateManager.getProvider();
var state = stateManager.getState();
mergedHooks = ObjectUtils.merge(
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getHooks());
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getMutableHooks());
EvaluationContext mergedCtx = hookSupport.beforeHooks(
var mergedCtx = hookSupport.beforeHooks(
type,
HookContext.from(
key,
@ -203,12 +191,20 @@ public class OpenFeatureClient implements Client {
afterHookContext =
HookContext.from(key, type, this.getMetadata(), provider.getMetadata(), mergedCtx, defaultValue);
ProviderEvaluation<T> providerEval =
// "short circuit" if the provider is in NOT_READY or FATAL state
if (ProviderState.NOT_READY.equals(state)) {
throw new ProviderNotReadyError("Provider not yet initialized");
}
if (ProviderState.FATAL.equals(state)) {
throw new FatalError("Provider is in an irrecoverable error state");
}
var providerEval =
(ProviderEvaluation<T>) createProviderEvaluation(type, key, defaultValue, provider, mergedCtx);
details = FlagEvaluationDetails.from(providerEval, key);
if (details.getErrorCode() != null) {
OpenFeatureError error =
var error =
ExceptionUtils.instantiateErrorByErrorCode(details.getErrorCode(), details.getErrorMessage());
enrichDetailsWithErrorDefaults(defaultValue, details);
hookSupport.errorHooks(type, afterHookContext, error, mergedHooks, hints);
@ -217,7 +213,7 @@ public class OpenFeatureClient implements Client {
}
} catch (Exception e) {
if (details == null) {
details = FlagEvaluationDetails.<T>builder().build();
details = FlagEvaluationDetails.<T>builder().flagKey(key).build();
}
if (e instanceof OpenFeatureError) {
details.setErrorCode(((OpenFeatureError) e).getErrorCode());
@ -262,7 +258,7 @@ public class OpenFeatureClient implements Client {
*/
private EvaluationContext mergeEvaluationContext(EvaluationContext invocationContext) {
final EvaluationContext apiContext = openfeatureApi.getEvaluationContext();
final EvaluationContext clientContext = this.getEvaluationContext();
final EvaluationContext clientContext = evaluationContext.get();
final EvaluationContext transactionContext = openfeatureApi.getTransactionContext();
return mergeContextMaps(apiContext, transactionContext, clientContext, invocationContext);
}
@ -507,7 +503,7 @@ public class OpenFeatureClient implements Client {
*/
@Override
public Client on(ProviderEvent event, Consumer<EventDetails> handler) {
OpenFeatureAPI.getInstance().addHandler(domain, event, handler);
openfeatureApi.addHandler(domain, event, handler);
return this;
}
@ -516,7 +512,7 @@ public class OpenFeatureClient implements Client {
*/
@Override
public Client removeHandler(ProviderEvent event, Consumer<EventDetails> handler) {
OpenFeatureAPI.getInstance().removeHandler(domain, event, handler);
openfeatureApi.removeHandler(domain, event, handler);
return this;
}
}

View File

@ -28,6 +28,11 @@ class ProviderRepository {
return thread;
});
private final Object registerStateManagerLock = new Object();
private final OpenFeatureAPI openFeatureAPI;
public ProviderRepository(OpenFeatureAPI openFeatureAPI) {
this.openFeatureAPI = openFeatureAPI;
}
FeatureProviderStateManager getFeatureProviderStateManager() {
return defaultStateManger.get();
@ -205,7 +210,7 @@ class ProviderRepository {
FeatureProviderStateManager oldManager) {
try {
if (ProviderState.NOT_READY.equals(newManager.getState())) {
newManager.initialize(OpenFeatureAPI.getInstance().getEvaluationContext());
newManager.initialize(openFeatureAPI.getEvaluationContext());
afterInit.accept(newManager.getProvider());
}
shutDownOld(oldManager, afterShutdown);

View File

@ -0,0 +1,95 @@
package dev.openfeature.sdk;
/**
* The Telemetry class provides constants and methods for creating OpenTelemetry compliant
* evaluation events.
*/
public class Telemetry {
private Telemetry() {}
/*
The OpenTelemetry compliant event attributes for flag evaluation.
Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
*/
public static final String TELEMETRY_KEY = "feature_flag.key";
public static final String TELEMETRY_ERROR_CODE = "error.type";
public static final String TELEMETRY_VARIANT = "feature_flag.result.variant";
public static final String TELEMETRY_VALUE = "feature_flag.result.value";
public static final String TELEMETRY_CONTEXT_ID = "feature_flag.context.id";
public static final String TELEMETRY_ERROR_MSG = "feature_flag.evaluation.error.message";
public static final String TELEMETRY_REASON = "feature_flag.result.reason";
public static final String TELEMETRY_PROVIDER = "feature_flag.provider.name";
public static final String TELEMETRY_FLAG_SET_ID = "feature_flag.set.id";
public static final String TELEMETRY_VERSION = "feature_flag.version";
// Well-known flag metadata attributes for telemetry events.
// Specification: https://openfeature.dev/specification/appendix-d#flag-metadata
public static final String TELEMETRY_FLAG_META_CONTEXT_ID = "contextId";
public static final String TELEMETRY_FLAG_META_FLAG_SET_ID = "flagSetId";
public static final String TELEMETRY_FLAG_META_VERSION = "version";
public static final String FLAG_EVALUATION_EVENT_NAME = "feature_flag.evaluation";
/**
* Creates an EvaluationEvent using the provided HookContext and ProviderEvaluation.
*
* @param hookContext the context containing flag evaluation details
* @param evaluationDetails the evaluation result from the provider
*
* @return an EvaluationEvent populated with telemetry data
*/
public static EvaluationEvent createEvaluationEvent(
HookContext<?> hookContext, FlagEvaluationDetails<?> evaluationDetails) {
EvaluationEvent.EvaluationEventBuilder evaluationEventBuilder = EvaluationEvent.builder()
.name(FLAG_EVALUATION_EVENT_NAME)
.attribute(TELEMETRY_KEY, hookContext.getFlagKey())
.attribute(TELEMETRY_PROVIDER, hookContext.getProviderMetadata().getName());
if (evaluationDetails.getReason() != null) {
evaluationEventBuilder.attribute(
TELEMETRY_REASON, evaluationDetails.getReason().toLowerCase());
} else {
evaluationEventBuilder.attribute(
TELEMETRY_REASON, Reason.UNKNOWN.name().toLowerCase());
}
if (evaluationDetails.getVariant() != null) {
evaluationEventBuilder.attribute(TELEMETRY_VARIANT, evaluationDetails.getVariant());
} else {
evaluationEventBuilder.attribute(TELEMETRY_VALUE, evaluationDetails.getValue());
}
String contextId = evaluationDetails.getFlagMetadata().getString(TELEMETRY_FLAG_META_CONTEXT_ID);
if (contextId != null) {
evaluationEventBuilder.attribute(TELEMETRY_CONTEXT_ID, contextId);
} else {
evaluationEventBuilder.attribute(
TELEMETRY_CONTEXT_ID, hookContext.getCtx().getTargetingKey());
}
String setID = evaluationDetails.getFlagMetadata().getString(TELEMETRY_FLAG_META_FLAG_SET_ID);
if (setID != null) {
evaluationEventBuilder.attribute(TELEMETRY_FLAG_SET_ID, setID);
}
String version = evaluationDetails.getFlagMetadata().getString(TELEMETRY_FLAG_META_VERSION);
if (version != null) {
evaluationEventBuilder.attribute(TELEMETRY_VERSION, version);
}
if (Reason.ERROR.name().equals(evaluationDetails.getReason())) {
if (evaluationDetails.getErrorCode() != null) {
evaluationEventBuilder.attribute(TELEMETRY_ERROR_CODE, evaluationDetails.getErrorCode());
} else {
evaluationEventBuilder.attribute(TELEMETRY_ERROR_CODE, ErrorCode.GENERAL);
}
if (evaluationDetails.getErrorMessage() != null) {
evaluationEventBuilder.attribute(TELEMETRY_ERROR_MSG, evaluationDetails.getErrorMessage());
}
}
return evaluationEventBuilder.build();
}
}

View File

@ -5,6 +5,7 @@ package dev.openfeature.sdk;
* for the duration of a single transaction.
* Examples of potential transaction specific context include: a user id, user agent, IP.
* Transaction context is merged with evaluation context prior to flag evaluation.
*
* <p>
* The precedence of merging context can be seen in
* <a href=https://openfeature.dev/specification/sections/evaluation-context#requirement-323>the specification</a>.

View File

@ -1,6 +1,7 @@
package dev.openfeature.sdk.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
@ -64,9 +65,9 @@ public class ObjectUtils {
* @return resulting object
*/
@SafeVarargs
public static <T> List<T> merge(List<T>... sources) {
public static <T> List<T> merge(Collection<T>... sources) {
List<T> merged = new ArrayList<>();
for (List<T> source : sources) {
for (Collection<T> source : sources) {
merged.addAll(source);
}
return merged;

View File

@ -1,5 +1,6 @@
package dev.openfeature.sdk.providers.memory;
import dev.openfeature.sdk.ImmutableMetadata;
import java.util.Map;
import lombok.Builder;
import lombok.Getter;
@ -18,4 +19,5 @@ public class Flag<T> {
private String defaultVariant;
private ContextEvaluator<T> contextEvaluator;
private ImmutableMetadata flagMetadata;
}

View File

@ -152,6 +152,7 @@ public class InMemoryProvider extends EventProvider {
.value(value)
.variant(flag.getDefaultVariant())
.reason(Reason.STATIC.toString())
.flagMetadata(flag.getFlagMetadata())
.build();
}
}

View File

@ -1,14 +1,12 @@
package dev.openfeature.sdk;
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
public class AlwaysBrokenWithDetailsProvider implements FeatureProvider {
private final String name = "always broken with details";
@Override
public Metadata getMetadata() {
return () -> {
throw new FlagNotFoundError(TestConstants.BROKEN_MESSAGE);
};
return () -> name;
}
@Override

View File

@ -2,7 +2,7 @@ package dev.openfeature.sdk;
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
public class AlwaysBrokenProvider implements FeatureProvider {
public class AlwaysBrokenWithExceptionProvider implements FeatureProvider {
private final String name = "always broken";

View File

@ -0,0 +1,75 @@
package dev.openfeature.sdk;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
@Timeout(value = 5, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
class AwaitableTest {
@Test
void waitingForFinishedIsANoOp() {
var startTime = System.currentTimeMillis();
Awaitable.FINISHED.await();
var endTime = System.currentTimeMillis();
assertTrue(endTime - startTime < 10);
}
@Test
void waitingForNotFinishedWaitsEvenWhenInterrupted() throws InterruptedException {
var awaitable = new Awaitable();
var mayProceed = new AtomicBoolean(false);
var thread = new Thread(() -> {
awaitable.await();
if (!mayProceed.get()) {
fail();
}
});
thread.start();
var startTime = System.currentTimeMillis();
do {
thread.interrupt();
} while (startTime + 1000 > System.currentTimeMillis());
mayProceed.set(true);
awaitable.wakeup();
thread.join();
}
@Test
void callingWakeUpWakesUpAllWaitingThreads() throws InterruptedException {
var awaitable = new Awaitable();
var isRunning = new AtomicInteger();
Runnable runnable = () -> {
isRunning.incrementAndGet();
var start = System.currentTimeMillis();
awaitable.await();
var end = System.currentTimeMillis();
if (end - start > 10) {
fail();
}
};
var numThreads = 2;
var threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
threads[i] = new Thread(runnable);
threads[i].start();
}
await().atMost(1, TimeUnit.SECONDS).until(() -> isRunning.get() == numThreads);
awaitable.wakeup();
for (int i = 0; i < numThreads; i++) {
threads[i].join();
}
}
}

View File

@ -2,17 +2,16 @@ package dev.openfeature.sdk;
import static org.junit.jupiter.api.Assertions.*;
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import org.junit.jupiter.api.Test;
class ClientProviderMappingTest {
@Test
void clientProviderTest() {
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
OpenFeatureAPI api = new OpenFeatureAPI();
FeatureProviderTestUtils.setFeatureProvider("client1", new DoSomethingProvider());
FeatureProviderTestUtils.setFeatureProvider("client2", new NoOpProvider());
api.setProviderAndWait("client1", new DoSomethingProvider());
api.setProviderAndWait("client2", new NoOpProvider());
Client c1 = api.getClient("client1");
Client c2 = api.getClient("client2");

View File

@ -8,7 +8,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import dev.openfeature.sdk.fixtures.HookFixtures;
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import dev.openfeature.sdk.testutils.TestEventsProvider;
import java.util.Arrays;
import java.util.HashMap;
@ -16,14 +15,20 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.SneakyThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class DeveloperExperienceTest implements HookFixtures {
transient String flagKey = "mykey";
private OpenFeatureAPI api;
@BeforeEach
public void setUp() throws Exception {
api = new OpenFeatureAPI();
}
@Test
void simpleBooleanFlag() {
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new TestEventsProvider());
Client client = api.getClient();
Boolean retval = client.getBooleanValue(flagKey, false);
@ -34,7 +39,6 @@ class DeveloperExperienceTest implements HookFixtures {
void clientHooks() {
Hook<Boolean> exampleHook = mockBooleanHook();
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new TestEventsProvider());
Client client = api.getClient();
client.addHooks(exampleHook);
@ -48,7 +52,6 @@ class DeveloperExperienceTest implements HookFixtures {
Hook<Boolean> clientHook = mockBooleanHook();
Hook<Boolean> evalHook = mockBooleanHook();
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new TestEventsProvider());
Client client = api.getClient();
client.addHooks(clientHook);
@ -69,7 +72,6 @@ class DeveloperExperienceTest implements HookFixtures {
@Test
void providingContext() {
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new TestEventsProvider());
Client client = api.getClient();
Map<String, Value> attributes = new HashMap<>();
@ -86,8 +88,7 @@ class DeveloperExperienceTest implements HookFixtures {
@Test
void brokenProvider() {
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client client = api.getClient();
FlagEvaluationDetails<Boolean> retval = client.getBooleanDetails(flagKey, false);
assertEquals(ErrorCode.FLAG_NOT_FOUND, retval.getErrorCode());
@ -99,6 +100,9 @@ class DeveloperExperienceTest implements HookFixtures {
@Test
void providerLockedPerTransaction() {
final String defaultValue = "string-value";
final OpenFeatureAPI api = new OpenFeatureAPI();
class MutatingHook implements Hook {
@Override
@ -106,16 +110,14 @@ class DeveloperExperienceTest implements HookFixtures {
// change the provider during a before hook - this should not impact the evaluation in progress
public Optional before(HookContext ctx, Map hints) {
FeatureProviderTestUtils.setFeatureProvider(TestEventsProvider.newInitializedTestEventsProvider());
api.setProviderAndWait(TestEventsProvider.newInitializedTestEventsProvider());
return Optional.empty();
}
}
final String defaultValue = "string-value";
final OpenFeatureAPI api = OpenFeatureAPI.getInstance();
final Client client = api.getClient();
FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider());
api.setProviderAndWait(new DoSomethingProvider());
api.addHooks(new MutatingHook());
// if provider is changed during an evaluation transaction it should proceed with the original provider
@ -132,7 +134,6 @@ class DeveloperExperienceTest implements HookFixtures {
@Test
void setProviderAndWaitShouldPutTheProviderInReadyState() {
String domain = "domain";
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(domain, new TestEventsProvider());
Client client = api.getClient(domain);
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
@ -145,12 +146,11 @@ class DeveloperExperienceTest implements HookFixtures {
@Test
void shouldPutTheProviderInStateErrorAfterEmittingErrorEvent() {
String domain = "domain";
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
TestEventsProvider provider = new TestEventsProvider();
api.setProviderAndWait(domain, provider);
Client client = api.getClient(domain);
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
provider.emitProviderError(ProviderEventDetails.builder().build());
provider.emitProviderError(ProviderEventDetails.builder().build()).await();
assertThat(client.getProviderState()).isEqualTo(ProviderState.ERROR);
}
@ -161,12 +161,11 @@ class DeveloperExperienceTest implements HookFixtures {
@Test
void shouldPutTheProviderInStateStaleAfterEmittingStaleEvent() {
String domain = "domain";
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
TestEventsProvider provider = new TestEventsProvider();
api.setProviderAndWait(domain, provider);
Client client = api.getClient(domain);
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
provider.emitProviderStale(ProviderEventDetails.builder().build());
provider.emitProviderStale(ProviderEventDetails.builder().build()).await();
assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
}
@ -177,14 +176,13 @@ class DeveloperExperienceTest implements HookFixtures {
@Test
void shouldPutTheProviderInStateReadyAfterEmittingReadyEvent() {
String domain = "domain";
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
TestEventsProvider provider = new TestEventsProvider();
api.setProviderAndWait(domain, provider);
Client client = api.getClient(domain);
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
provider.emitProviderStale(ProviderEventDetails.builder().build());
provider.emitProviderStale(ProviderEventDetails.builder().build()).await();
assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
provider.emitProviderReady(ProviderEventDetails.builder().build());
provider.emitProviderReady(ProviderEventDetails.builder().build()).await();
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
}
}

View File

@ -28,10 +28,11 @@ class EventProviderTest {
@AfterAll
public static void resetDefaultProvider() {
OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
new OpenFeatureAPI().setProviderAndWait(new NoOpProvider());
}
@Test
@Timeout(value = 2, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
@DisplayName("should run attached onEmit with emitters")
void emitsEventsWhenAttached() {
TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit = mockOnEmit();
@ -91,7 +92,7 @@ class EventProviderTest {
@DisplayName("should not deadlock on emit called during emit")
void doesNotDeadlockOnEmitStackedCalls() {
TestStackedEmitCallsProvider provider = new TestStackedEmitCallsProvider();
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
new OpenFeatureAPI().setProviderAndWait(provider);
}
static class TestEventProvider extends EventProvider {

View File

@ -7,11 +7,11 @@ import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.*;
import dev.openfeature.sdk.testutils.TestEventsProvider;
import io.cucumber.java.AfterAll;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import lombok.SneakyThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@ -21,10 +21,11 @@ class EventsTest {
private static final int TIMEOUT = 500;
private static final int INIT_DELAY = TIMEOUT / 2;
private OpenFeatureAPI api;
@AfterAll
public static void resetDefaultProvider() {
OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
@BeforeEach
void setUp() {
api = new OpenFeatureAPI();
}
@Nested
@ -49,8 +50,8 @@ class EventsTest {
final String name = "apiInitReady";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().onProviderReady(handler);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
api.onProviderReady(handler);
api.setProviderAndWait(name, provider);
verify(handler, timeout(TIMEOUT).atLeastOnce()).accept(any());
}
@ -66,8 +67,8 @@ class EventsTest {
final String errMessage = "oh no!";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY, true, errMessage);
OpenFeatureAPI.getInstance().onProviderError(handler);
OpenFeatureAPI.getInstance().setProvider(name, provider);
api.onProviderError(handler);
api.setProvider(name, provider);
verify(handler, timeout(TIMEOUT)).accept(argThat(details -> {
return errMessage.equals(details.getMessage());
}));
@ -89,8 +90,8 @@ class EventsTest {
final String name = "apiShouldPropagateEvents";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler);
api.setProviderAndWait(name, provider);
api.onProviderConfigurationChanged(handler);
provider.mockEvent(
ProviderEvent.PROVIDER_CONFIGURATION_CHANGED,
@ -118,12 +119,12 @@ class EventsTest {
final Consumer<EventDetails> handler4 = mockHandler();
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
api.setProviderAndWait(name, provider);
OpenFeatureAPI.getInstance().onProviderReady(handler1);
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler2);
OpenFeatureAPI.getInstance().onProviderStale(handler3);
OpenFeatureAPI.getInstance().onProviderError(handler4);
api.onProviderReady(handler1);
api.onProviderConfigurationChanged(handler2);
api.onProviderStale(handler3);
api.onProviderError(handler4);
Arrays.asList(ProviderEvent.values()).stream().forEach(eventType -> {
provider.mockEvent(
@ -162,8 +163,8 @@ class EventsTest {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
// set provider before getting a client
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
Client client = OpenFeatureAPI.getInstance().getClient();
api.setProviderAndWait(provider);
Client client = api.getClient();
client.onProviderStale(handler);
provider.mockEvent(
@ -183,8 +184,8 @@ class EventsTest {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
// set provider before getting a client
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
Client client = OpenFeatureAPI.getInstance().getClient(name);
api.setProviderAndWait(provider);
Client client = api.getClient(name);
client.onProviderStale(handler);
provider.mockEvent(
@ -213,10 +214,10 @@ class EventsTest {
final String name = "initReadyProviderBefore";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
Client client = OpenFeatureAPI.getInstance().getClient(name);
Client client = api.getClient(name);
client.onProviderReady(handler);
// set provider after getting a client
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
api.setProviderAndWait(name, provider);
verify(handler, timeout(TIMEOUT).atLeastOnce())
.accept(argThat(details -> details.getDomain().equals(name)));
}
@ -233,8 +234,8 @@ class EventsTest {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
// set provider before getting a client
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
Client client = OpenFeatureAPI.getInstance().getClient(name);
api.setProviderAndWait(name, provider);
Client client = api.getClient(name);
client.onProviderReady(handler);
verify(handler, timeout(TIMEOUT).atLeastOnce())
.accept(argThat(details -> details.getDomain().equals(name)));
@ -252,10 +253,10 @@ class EventsTest {
final String errMessage = "oh no!";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY, true, errMessage);
Client client = OpenFeatureAPI.getInstance().getClient(name);
Client client = api.getClient(name);
client.onProviderError(handler);
// set provider after getting a client
OpenFeatureAPI.getInstance().setProvider(name, provider);
api.setProvider(name, provider);
verify(handler, timeout(TIMEOUT)).accept(argThat(details -> {
return name.equals(details.getDomain()) && errMessage.equals(details.getMessage());
}));
@ -274,8 +275,8 @@ class EventsTest {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY, true, errMessage);
// set provider after getting a client
OpenFeatureAPI.getInstance().setProvider(name, provider);
Client client = OpenFeatureAPI.getInstance().getClient(name);
api.setProvider(name, provider);
Client client = api.getClient(name);
client.onProviderError(handler);
verify(handler, timeout(TIMEOUT)).accept(argThat(details -> {
return name.equals(details.getDomain()) && errMessage.equals(details.getMessage());
@ -299,8 +300,8 @@ class EventsTest {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
// set provider before getting a client
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
Client client = OpenFeatureAPI.getInstance().getClient(name);
api.setProviderAndWait(name, provider);
Client client = api.getClient(name);
client.onProviderConfigurationChanged(handler);
provider.mockEvent(
@ -322,10 +323,10 @@ class EventsTest {
final String name = "shouldPropagateAfter";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
Client client = OpenFeatureAPI.getInstance().getClient(name);
Client client = api.getClient(name);
client.onProviderConfigurationChanged(handler);
// set provider after getting a client
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
api.setProviderAndWait(name, provider);
provider.mockEvent(
ProviderEvent.PROVIDER_CONFIGURATION_CHANGED,
@ -354,8 +355,8 @@ class EventsTest {
final Consumer<EventDetails> handler4 = mockHandler();
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
Client client = OpenFeatureAPI.getInstance().getClient(name);
api.setProviderAndWait(name, provider);
Client client = api.getClient(name);
client.onProviderReady(handler1);
client.onProviderConfigurationChanged(handler2);
@ -384,14 +385,14 @@ class EventsTest {
TestEventsProvider provider1 = new TestEventsProvider(INIT_DELAY);
TestEventsProvider provider2 = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider1);
Client client = OpenFeatureAPI.getInstance().getClient(name);
api.setProviderAndWait(name, provider1);
Client client = api.getClient(name);
// attached handlers
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler1);
api.onProviderConfigurationChanged(handler1);
client.onProviderConfigurationChanged(handler2);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider2);
api.setProviderAndWait(name, provider2);
// wait for the new provider to be ready and make sure things are cleaned up.
await().until(() -> provider1.isShutDown());
@ -421,11 +422,11 @@ class EventsTest {
TestEventsProvider provider1 = new TestEventsProvider(INIT_DELAY);
TestEventsProvider provider2 = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(name1, provider1);
OpenFeatureAPI.getInstance().setProviderAndWait(name2, provider2);
api.setProviderAndWait(name1, provider1);
api.setProviderAndWait(name2, provider2);
Client client1 = OpenFeatureAPI.getInstance().getClient(name1);
Client client2 = OpenFeatureAPI.getInstance().getClient(name2);
Client client1 = api.getClient(name1);
Client client2 = api.getClient(name2);
client1.onProviderConfigurationChanged(handlerToRun);
client2.onProviderConfigurationChanged(handlerNotToRun);
@ -450,11 +451,11 @@ class EventsTest {
TestEventsProvider namedProvider = new TestEventsProvider(INIT_DELAY);
TestEventsProvider defaultProvider = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(defaultProvider);
api.setProviderAndWait(defaultProvider);
Client client = OpenFeatureAPI.getInstance().getClient(name);
Client client = api.getClient(name);
client.onProviderConfigurationChanged(handlerNotToRun);
OpenFeatureAPI.getInstance().setProviderAndWait(name, namedProvider);
api.setProviderAndWait(name, namedProvider);
// await the new provider to make sure the old one is shut down
await().until(() -> namedProvider.getState().equals(ProviderState.READY));
@ -465,7 +466,7 @@ class EventsTest {
ProviderEventDetails.builder().build());
verify(handlerNotToRun, after(TIMEOUT).never()).accept(any());
OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
api.setProviderAndWait(new NoOpProvider());
}
@Test
@ -479,9 +480,9 @@ class EventsTest {
final Consumer<EventDetails> handlerToRun = mockHandler();
TestEventsProvider defaultProvider = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(defaultProvider);
api.setProviderAndWait(defaultProvider);
Client client = OpenFeatureAPI.getInstance().getClient(name);
Client client = api.getClient(name);
client.onProviderConfigurationChanged(handlerToRun);
// await the new provider to make sure the old one is shut down
@ -493,7 +494,7 @@ class EventsTest {
ProviderEventDetails.builder().build());
verify(handlerToRun, timeout(TIMEOUT)).accept(any());
OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
api.setProviderAndWait(new NoOpProvider());
}
@Test
@ -509,9 +510,9 @@ class EventsTest {
final Consumer<EventDetails> lastHandler = mockHandler();
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
api.setProviderAndWait(name, provider);
Client client1 = OpenFeatureAPI.getInstance().getClient(name);
Client client1 = api.getClient(name);
client1.onProviderConfigurationChanged(errorHandler);
client1.onProviderConfigurationChanged(nextHandler);
@ -537,11 +538,11 @@ class EventsTest {
final String name = "shouldHaveAllProperties";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
Client client = OpenFeatureAPI.getInstance().getClient(name);
api.setProviderAndWait(name, provider);
Client client = api.getClient(name);
// attached handlers
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler1);
api.onProviderConfigurationChanged(handler1);
client.onProviderConfigurationChanged(handler2);
List<String> flagsChanged = Arrays.asList("flag");
@ -577,15 +578,15 @@ class EventsTest {
number = "5.3.3",
text = "Handlers attached after the provider is already in the associated state, MUST run immediately.")
void matchingReadyEventsMustRunImmediately() {
final String name = "matchingEventsMustRunImmediately";
final String name = "matchingReadyEventsMustRunImmediately";
final Consumer<EventDetails> handler = mockHandler();
// provider which is already ready
TestEventsProvider provider = new TestEventsProvider();
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
api.setProviderAndWait(name, provider);
// should run even thought handler was added after ready
Client client = OpenFeatureAPI.getInstance().getClient(name);
Client client = api.getClient(name);
client.onProviderReady(handler);
verify(handler, timeout(TIMEOUT)).accept(any());
}
@ -596,15 +597,14 @@ class EventsTest {
number = "5.3.3",
text = "Handlers attached after the provider is already in the associated state, MUST run immediately.")
void matchingStaleEventsMustRunImmediately() {
final String name = "matchingEventsMustRunImmediately";
final String name = "matchingStaleEventsMustRunImmediately";
final Consumer<EventDetails> handler = mockHandler();
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
// provider which is already stale
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
Client client = api.getClient(name);
api.setProviderAndWait(name, provider);
provider.emitProviderStale(ProviderEventDetails.builder().build());
provider.emitProviderStale(ProviderEventDetails.builder().build()).await();
assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
// should run even though handler was added after stale
@ -618,17 +618,17 @@ class EventsTest {
number = "5.3.3",
text = "Handlers attached after the provider is already in the associated state, MUST run immediately.")
void matchingErrorEventsMustRunImmediately() {
final String name = "matchingEventsMustRunImmediately";
final String name = "matchingErrorEventsMustRunImmediately";
final Consumer<EventDetails> handler = mockHandler();
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
// provider which is already in error
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
Client client = api.getClient(name);
api.setProviderAndWait(name, provider);
provider.emitProviderError(ProviderEventDetails.builder().build());
provider.emitProviderError(ProviderEventDetails.builder().build()).await();
assertThat(client.getProviderState()).isEqualTo(ProviderState.ERROR);
verify(handler, never()).accept(any());
// should run even though handler was added after error
client.onProviderError(handler);
verify(handler, timeout(TIMEOUT)).accept(any());
@ -644,8 +644,8 @@ class EventsTest {
TestEventsProvider provider1 = new TestEventsProvider(INIT_DELAY);
TestEventsProvider provider2 = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider1);
Client client = OpenFeatureAPI.getInstance().getClient(name);
api.setProviderAndWait(name, provider1);
Client client = api.getClient(name);
client.onProviderConfigurationChanged(handler);
provider1.mockEvent(
@ -657,7 +657,7 @@ class EventsTest {
verify(handler, timeout(TIMEOUT).times(1)).accept(argThat(nameMatches));
// wait for the new provider to be ready.
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider2);
api.setProviderAndWait(name, provider2);
// verify that with the new provider under the same name, the handler is called
// again.
@ -681,14 +681,14 @@ class EventsTest {
final Consumer<EventDetails> handler2 = mockHandler();
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
Client client = OpenFeatureAPI.getInstance().getClient(name);
api.setProviderAndWait(name, provider);
Client client = api.getClient(name);
// attached handlers
OpenFeatureAPI.getInstance().onProviderStale(handler1);
api.onProviderStale(handler1);
client.onProviderConfigurationChanged(handler2);
OpenFeatureAPI.getInstance().removeHandler(ProviderEvent.PROVIDER_STALE, handler1);
api.removeHandler(ProviderEvent.PROVIDER_STALE, handler1);
client.removeHandler(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, handler2);
// emit event

View File

@ -0,0 +1,45 @@
package dev.openfeature.sdk;
import dev.openfeature.sdk.exceptions.FatalError;
import dev.openfeature.sdk.exceptions.GeneralError;
public class FatalErrorProvider implements FeatureProvider {
private final String name = "fatal";
@Override
public Metadata getMetadata() {
return () -> name;
}
@Override
public void initialize(EvaluationContext evaluationContext) throws Exception {
throw new FatalError(); // throw a fatal error on startup (this will cause the SDK to short circuit evaluations)
}
@Override
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
@Override
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
@Override
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
@Override
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
@Override
public ProviderEvaluation<Value> getObjectEvaluation(
String key, Value defaultValue, EvaluationContext invocationContext) {
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
}
}

View File

@ -9,7 +9,6 @@ import static org.mockito.Mockito.*;
import dev.openfeature.sdk.exceptions.GeneralError;
import dev.openfeature.sdk.fixtures.HookFixtures;
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import dev.openfeature.sdk.testutils.TestEventsProvider;
import java.util.HashMap;
import java.util.List;
@ -29,7 +28,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
private OpenFeatureAPI api;
private Client _client() {
FeatureProviderTestUtils.setFeatureProvider(new NoOpProvider());
api.setProviderAndWait(new NoOpProvider());
return api.getClient();
}
@ -37,18 +36,13 @@ class FlagEvaluationSpecTest implements HookFixtures {
private Client _initializedClient() {
TestEventsProvider provider = new TestEventsProvider();
provider.initialize(null);
FeatureProviderTestUtils.setFeatureProvider(provider);
api.setProviderAndWait(provider);
return api.getClient();
}
@BeforeEach
void getApiInstance() {
api = OpenFeatureAPI.getInstance();
}
@AfterEach
void reset_ctx() {
api.setEvaluationContext(null);
api = new OpenFeatureAPI();
}
@BeforeEach
@ -62,15 +56,6 @@ class FlagEvaluationSpecTest implements HookFixtures {
LoggerMock.setMock(OpenFeatureClient.class, logger);
}
@Specification(
number = "1.1.1",
text =
"The API, and any state it maintains SHOULD exist as a global singleton, even in cases wherein multiple versions of the API are present at runtime.")
@Test
void global_singleton() {
assertSame(OpenFeatureAPI.getInstance(), OpenFeatureAPI.getInstance());
}
@Specification(
number = "1.1.2.1",
text =
@ -78,7 +63,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
@Test
void provider() {
FeatureProvider mockProvider = mock(FeatureProvider.class);
FeatureProviderTestUtils.setFeatureProvider(mockProvider);
api.setProviderAndWait(mockProvider);
assertThat(api.getProvider()).isEqualTo(mockProvider);
}
@ -90,13 +75,13 @@ class FlagEvaluationSpecTest implements HookFixtures {
@Test
void providerAndWait() {
FeatureProvider provider = new TestEventsProvider(500);
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
api.setProviderAndWait(provider);
Client client = api.getClient();
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
provider = new TestEventsProvider(500);
String providerName = "providerAndWait";
OpenFeatureAPI.getInstance().setProviderAndWait(providerName, provider);
api.setProviderAndWait(providerName, provider);
Client client2 = api.getClient(providerName);
assertThat(client2.getProviderState()).isEqualTo(ProviderState.READY);
}
@ -124,8 +109,8 @@ class FlagEvaluationSpecTest implements HookFixtures {
void shouldReturnNotReadyIfNotInitialized() {
FeatureProvider provider = new TestEventsProvider(100);
String providerName = "shouldReturnNotReadyIfNotInitialized";
OpenFeatureAPI.getInstance().setProvider(providerName, provider);
Client client = OpenFeatureAPI.getInstance().getClient(providerName);
api.setProvider(providerName, provider);
Client client = api.getClient(providerName);
FlagEvaluationDetails<Boolean> details = client.getBooleanDetails("return_error_when_not_initialized", false);
assertEquals(ErrorCode.PROVIDER_NOT_READY, details.getErrorCode());
assertEquals(Reason.ERROR.toString(), details.getReason());
@ -136,7 +121,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
text = "The API MUST provide a function for retrieving the metadata field of the configured provider.")
@Test
void provider_metadata() {
FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider());
api.setProviderAndWait(new DoSomethingProvider());
assertThat(api.getProviderMetadata().getName()).isEqualTo(DoSomethingProvider.name);
}
@ -198,7 +183,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
"The client SHOULD provide functions for floating-point numbers and integers, consistent with language idioms.")
@Test
void value_flags() {
FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider());
api.setProviderAndWait(new DoSomethingProvider());
Client c = api.getClient();
String key = "key";
@ -279,7 +264,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
"In cases of normal execution, the `evaluation details` structure's `reason` field MUST contain the value of the `reason` field in the `flag resolution` structure returned by the configured `provider`, if the field is set.")
@Test
void detail_flags() {
FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider());
api.setProviderAndWait(new DoSomethingProvider());
Client c = api.getClient();
String key = "key";
@ -386,7 +371,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
"In cases of abnormal execution, the `evaluation details` structure's `error message` field **MAY** contain a string containing additional details about the nature of the error.")
@Test
void broken_provider() {
FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c = api.getClient();
boolean defaultValue = false;
assertFalse(c.getBooleanValue("key", defaultValue));
@ -414,8 +399,8 @@ class FlagEvaluationSpecTest implements HookFixtures {
text =
"In cases of abnormal execution, the `evaluation details` structure's `error message` field **MAY** contain a string containing additional details about the nature of the error.")
@Test
void broken_provider_withDetails() {
FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenWithDetailsProvider());
void broken_provider_withDetails() throws InterruptedException {
api.setProviderAndWait(new AlwaysBrokenWithDetailsProvider());
Client c = api.getClient();
boolean defaultValue = false;
assertFalse(c.getBooleanValue("key", defaultValue));
@ -431,7 +416,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
text = "Methods, functions, or operations on the client SHOULD NOT write log messages.")
@Test
void log_on_error() throws NotImplementedException {
FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c = api.getClient();
FlagEvaluationDetails<Boolean> result = c.getBooleanDetails("test", false);
@ -450,7 +435,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
assertNull(c.getMetadata().getDomain());
String domainName = "test domain";
FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c2 = api.getClient(domainName);
assertEquals(domainName, c2.getMetadata().getName());
@ -463,7 +448,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
"In cases of abnormal execution (network failure, unhandled error, etc) the reason field in the evaluation details SHOULD indicate an error.")
@Test
void reason_is_error_when_there_are_errors() {
FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c = api.getClient();
FlagEvaluationDetails<Boolean> result = c.getBooleanDetails("test", false);
assertEquals(Reason.ERROR.toString(), result.getReason());
@ -475,7 +460,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
"If the flag metadata field in the flag resolution structure returned by the configured provider is set, the evaluation details structure's flag metadata field MUST contain that value. Otherwise, it MUST contain an empty record.")
@Test
void flag_metadata_passed() {
FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider(null));
api.setProviderAndWait(new DoSomethingProvider(null));
Client c = api.getClient();
FlagEvaluationDetails<Boolean> result = c.getBooleanDetails("test", false);
assertNotNull(result.getFlagMetadata());
@ -487,7 +472,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
String contextKey = "some-key";
String contextValue = "some-value";
DoSomethingProvider provider = spy(new DoSomethingProvider());
FeatureProviderTestUtils.setFeatureProvider(provider);
api.setProviderAndWait(provider);
Map<String, Value> attributes = new HashMap<>();
attributes.put(contextKey, new Value(contextValue));
@ -514,7 +499,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
@Test
void multi_layer_context_merges_correctly() {
DoSomethingProvider provider = spy(new DoSomethingProvider());
FeatureProviderTestUtils.setFeatureProvider(provider);
api.setProviderAndWait(provider);
TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator();
api.setTransactionContextPropagator(transactionContextPropagator);
Hook<Boolean> hook = spy(new Hook<Boolean>() {
@ -702,7 +687,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
@Test
void setting_transaction_context_propagator() {
DoSomethingProvider provider = new DoSomethingProvider();
FeatureProviderTestUtils.setFeatureProvider(provider);
api.setProviderAndWait(provider);
TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator();
api.setTransactionContextPropagator(transactionContextPropagator);
@ -716,7 +701,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
@Test
void setting_transaction_context() {
DoSomethingProvider provider = new DoSomethingProvider();
FeatureProviderTestUtils.setFeatureProvider(provider);
api.setProviderAndWait(provider);
TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator();
api.setTransactionContextPropagator(transactionContextPropagator);

View File

@ -1,6 +1,8 @@
package dev.openfeature.sdk;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -9,7 +11,7 @@ class FlagMetadataTest {
@Test
@DisplayName("Test metadata payload construction and retrieval")
public void builder_validation() {
void builder_validation() {
// given
ImmutableMetadata flagMetadata = ImmutableMetadata.builder()
.addString("string", "string")
@ -42,7 +44,7 @@ class FlagMetadataTest {
@Test
@DisplayName("Value type mismatch returns a null")
public void value_type_validation() {
void value_type_validation() {
// given
ImmutableMetadata flagMetadata =
ImmutableMetadata.builder().addString("string", "string").build();
@ -53,11 +55,34 @@ class FlagMetadataTest {
@Test
@DisplayName("A null is returned if key does not exist")
public void notfound_error_validation() {
void notfound_error_validation() {
// given
ImmutableMetadata flagMetadata = ImmutableMetadata.builder().build();
// then
assertThat(flagMetadata.getBoolean("string")).isNull();
}
@Test
@DisplayName("isEmpty and isNotEmpty return correctly when the metadata is empty")
void isEmpty_isNotEmpty_return_correctly_when_metadata_is_empty() {
// given
ImmutableMetadata flagMetadata = ImmutableMetadata.builder().build();
// then
assertTrue(flagMetadata.isEmpty());
assertFalse(flagMetadata.isNotEmpty());
}
@Test
@DisplayName("isEmpty and isNotEmpty return correctly when the metadata is not empty")
void isEmpty_isNotEmpty_return_correctly_when_metadata_is_not_empty() {
// given
ImmutableMetadata flagMetadata =
ImmutableMetadata.builder().addString("a", "b").build();
// then
assertFalse(flagMetadata.isEmpty());
assertTrue(flagMetadata.isNotEmpty());
}
}

View File

@ -18,7 +18,6 @@ import static org.mockito.Mockito.when;
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
import dev.openfeature.sdk.fixtures.HookFixtures;
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import dev.openfeature.sdk.testutils.TestEventsProvider;
import java.util.ArrayList;
import java.util.Arrays;
@ -28,16 +27,18 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
class HookSpecTest implements HookFixtures {
@AfterEach
void emptyApiHooks() {
// it's a singleton. Don't pollute each test.
OpenFeatureAPI.getInstance().clearHooks();
private OpenFeatureAPI api;
@BeforeEach
void setUp() {
this.api = new OpenFeatureAPI();
}
@Specification(
@ -163,7 +164,7 @@ class HookSpecTest implements HookFixtures {
.type(FlagValueType.INTEGER)
.ctx(new ImmutableContext())
.defaultValue(1)
.clientMetadata(OpenFeatureAPI.getInstance().getClient().getMetadata())
.clientMetadata(api.getClient().getMetadata())
.build();
}
@ -173,8 +174,8 @@ class HookSpecTest implements HookFixtures {
"The before stage MUST run before flag resolution occurs. It accepts a hook context (required) and hook hints (optional) as parameters and returns either an evaluation context or nothing.")
@Test
void before_runs_ahead_of_evaluation() {
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new AlwaysBrokenProvider());
api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client client = api.getClient();
Hook<Boolean> evalHook = mockBooleanHook();
@ -216,8 +217,7 @@ class HookSpecTest implements HookFixtures {
.errorMessage(errorMessage)
.build());
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
FeatureProviderTestUtils.setFeatureProvider("errorHookMustRun", provider);
api.setProviderAndWait("errorHookMustRun", provider);
Client client = api.getClient("errorHookMustRun");
client.getBooleanValue(
"key",
@ -259,7 +259,7 @@ class HookSpecTest implements HookFixtures {
@Test
void hook_eval_order() {
List<String> evalOrder = new ArrayList<>();
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait("evalOrder", new TestEventsProvider() {
public List<Hook> getProviderHooks() {
return Collections.singletonList(new BooleanHook() {
@ -411,8 +411,7 @@ class HookSpecTest implements HookFixtures {
doThrow(RuntimeException.class).when(h).before(any(), any());
Hook<Boolean> h2 = mockBooleanHook();
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new AlwaysBrokenProvider());
api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c = api.getClient();
c.getBooleanDetails(
@ -516,8 +515,7 @@ class HookSpecTest implements HookFixtures {
.thenReturn(ProviderEvaluation.<Boolean>builder().value(true).build());
InOrder order = inOrder(hook, provider);
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
FeatureProviderTestUtils.setFeatureProvider(provider);
api.setProviderAndWait(provider);
Client client = api.getClient();
client.getBooleanValue(
"key",
@ -596,6 +594,25 @@ class HookSpecTest implements HookFixtures {
assertThat(evaluationDetails.getValue()).isTrue();
}
@Test
void shortCircuit_flagResolution_runsHooksWithAllFields() {
String domain = "shortCircuit_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails";
api.setProvider(domain, new FatalErrorProvider());
Hook hook = mockBooleanHook();
String flagKey = "test-flag-key";
Client client = api.getClient(domain);
client.getBooleanValue(
flagKey,
true,
new ImmutableContext(),
FlagEvaluationOptions.builder().hook(hook).build());
verify(hook).before(any(), any());
verify(hook).error(any(HookContext.class), any(Exception.class), any(Map.class));
verify(hook).finallyAfter(any(HookContext.class), any(FlagEvaluationDetails.class), any(Map.class));
}
@Test
void successful_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails() {
Hook hook = mockBooleanHook();
@ -695,8 +712,7 @@ class HookSpecTest implements HookFixtures {
when(provider.getBooleanEvaluation(any(), any(), any()))
.thenReturn(ProviderEvaluation.<Boolean>builder().value(true).build());
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
FeatureProviderTestUtils.setFeatureProvider(provider);
api.setProviderAndWait(provider);
Client client = api.getClient();
client.getBooleanValue(
"key",
@ -761,11 +777,10 @@ class HookSpecTest implements HookFixtures {
}
private Client getClient(FeatureProvider provider) {
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
if (provider == null) {
FeatureProviderTestUtils.setFeatureProvider(TestEventsProvider.newInitializedTestEventsProvider());
api.setProviderAndWait(TestEventsProvider.newInitializedTestEventsProvider());
} else {
FeatureProviderTestUtils.setFeatureProvider(provider);
api.setProviderAndWait(provider);
}
return api.getClient();
}

View File

@ -3,6 +3,7 @@ package dev.openfeature.sdk;
import static dev.openfeature.sdk.EvaluationContext.TARGETING_KEY;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collections;
@ -133,4 +134,31 @@ class ImmutableContextTest {
Structure value = key1.asStructure();
assertArrayEquals(new Object[] {"key1_1"}, value.keySet().toArray());
}
@DisplayName("Two different MutableContext objects with the different contents are not considered equal")
@Test
void unequalImmutableContextsAreNotEqual() {
final Map<String, Value> attributes = new HashMap<>();
attributes.put("key1", new Value("val1"));
final ImmutableContext ctx = new ImmutableContext(attributes);
final Map<String, Value> attributes2 = new HashMap<>();
final ImmutableContext ctx2 = new ImmutableContext(attributes2);
assertNotEquals(ctx, ctx2);
}
@DisplayName("Two different MutableContext objects with the same content are considered equal")
@Test
void equalImmutableContextsAreEqual() {
final Map<String, Value> attributes = new HashMap<>();
attributes.put("key1", new Value("val1"));
final ImmutableContext ctx = new ImmutableContext(attributes);
final Map<String, Value> attributes2 = new HashMap<>();
attributes2.put("key1", new Value("val1"));
final ImmutableContext ctx2 = new ImmutableContext(attributes2);
assertEquals(ctx, ctx2);
}
}

View File

@ -0,0 +1,28 @@
package dev.openfeature.sdk;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.Test;
class ImmutableMetadataTest {
@Test
void unequalImmutableMetadataAreUnequal() {
ImmutableMetadata i1 =
ImmutableMetadata.builder().addString("key1", "value1").build();
ImmutableMetadata i2 =
ImmutableMetadata.builder().addString("key1", "value2").build();
assertNotEquals(i1, i2);
}
@Test
void equalImmutableMetadataAreEqual() {
ImmutableMetadata i1 =
ImmutableMetadata.builder().addString("key1", "value1").build();
ImmutableMetadata i2 =
ImmutableMetadata.builder().addString("key1", "value1").build();
assertEquals(i1, i2);
}
}

View File

@ -1,6 +1,11 @@
package dev.openfeature.sdk;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@ -154,4 +159,42 @@ class ImmutableStructureTest {
attrs.put("null", null);
new ImmutableStructure(attrs);
}
@Test
void unequalImmutableStructuresAreNotEqual() {
Map<String, Value> attrs1 = new HashMap<>();
attrs1.put("test", new Value(45));
ImmutableStructure structure1 = new ImmutableStructure(attrs1);
Map<String, Value> attrs2 = new HashMap<>();
attrs2.put("test", new Value(2));
ImmutableStructure structure2 = new ImmutableStructure(attrs2);
assertNotEquals(structure1, structure2);
}
@Test
void equalImmutableStructuresAreEqual() {
Map<String, Value> attrs1 = new HashMap<>();
attrs1.put("test", new Value(45));
ImmutableStructure structure1 = new ImmutableStructure(attrs1);
Map<String, Value> attrs2 = new HashMap<>();
attrs2.put("test", new Value(45));
ImmutableStructure structure2 = new ImmutableStructure(attrs2);
assertEquals(structure1, structure2);
}
@Test
void emptyImmutableStructureIsEmpty() {
ImmutableStructure m1 = new ImmutableStructure();
assertTrue(m1.isEmpty());
}
@Test
void immutableStructureWithNullAttributesIsEmpty() {
ImmutableStructure m1 = new ImmutableStructure(null);
assertTrue(m1.isEmpty());
}
}

View File

@ -17,10 +17,12 @@ import org.junit.jupiter.api.Test;
class InitializeBehaviorSpecTest {
private static final String DOMAIN_NAME = "mydomain";
private OpenFeatureAPI api;
@BeforeEach
void setupTest() {
OpenFeatureAPI.getInstance().setProvider(new NoOpProvider());
this.api = new OpenFeatureAPI();
api.setProvider(new NoOpProvider());
}
@Nested
@ -37,7 +39,7 @@ class InitializeBehaviorSpecTest {
FeatureProvider featureProvider = mock(FeatureProvider.class);
doReturn(ProviderState.NOT_READY).when(featureProvider).getState();
OpenFeatureAPI.getInstance().setProvider(featureProvider);
api.setProvider(featureProvider);
verify(featureProvider, timeout(1000)).initialize(any());
}
@ -55,8 +57,7 @@ class InitializeBehaviorSpecTest {
doReturn(ProviderState.NOT_READY).when(featureProvider).getState();
doThrow(TestException.class).when(featureProvider).initialize(any());
assertThatCode(() -> OpenFeatureAPI.getInstance().setProvider(featureProvider))
.doesNotThrowAnyException();
assertThatCode(() -> api.setProvider(featureProvider)).doesNotThrowAnyException();
verify(featureProvider, timeout(1000)).initialize(any());
}
@ -77,7 +78,7 @@ class InitializeBehaviorSpecTest {
FeatureProvider featureProvider = mock(FeatureProvider.class);
doReturn(ProviderState.NOT_READY).when(featureProvider).getState();
OpenFeatureAPI.getInstance().setProvider(DOMAIN_NAME, featureProvider);
api.setProvider(DOMAIN_NAME, featureProvider);
verify(featureProvider, timeout(1000)).initialize(any());
}
@ -95,8 +96,7 @@ class InitializeBehaviorSpecTest {
doReturn(ProviderState.NOT_READY).when(featureProvider).getState();
doThrow(TestException.class).when(featureProvider).initialize(any());
assertThatCode(() -> OpenFeatureAPI.getInstance().setProvider(DOMAIN_NAME, featureProvider))
.doesNotThrowAnyException();
assertThatCode(() -> api.setProvider(DOMAIN_NAME, featureProvider)).doesNotThrowAnyException();
verify(featureProvider, timeout(1000)).initialize(any());
}

View File

@ -15,12 +15,11 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Isolated;
@Isolated()
class LockingTest {
class LockingSingeltonTest {
private static OpenFeatureAPI api;
private OpenFeatureClient client;
private AutoCloseableReentrantReadWriteLock apiLock;
private AutoCloseableReentrantReadWriteLock clientContextLock;
private AutoCloseableReentrantReadWriteLock clientHooksLock;
@BeforeAll
@ -36,10 +35,7 @@ class LockingTest {
apiLock = setupLock(apiLock, mockInnerReadLock(), mockInnerWriteLock());
OpenFeatureAPI.lock = apiLock;
clientContextLock = setupLock(clientContextLock, mockInnerReadLock(), mockInnerWriteLock());
clientHooksLock = setupLock(clientHooksLock, mockInnerReadLock(), mockInnerWriteLock());
client.contextLock = clientContextLock;
client.hooksLock = clientHooksLock;
}
@Nested
@ -137,50 +133,6 @@ class LockingTest {
}
}
@Test
void addHooksShouldWriteLockAndUnlock() {
client.addHooks(new Hook() {});
verify(clientHooksLock.writeLock()).lock();
verify(clientHooksLock.writeLock()).unlock();
api.addHooks(new Hook() {});
verify(apiLock.writeLock()).lock();
verify(apiLock.writeLock()).unlock();
}
@Test
void getHooksShouldReadLockAndUnlock() {
client.getHooks();
verify(clientHooksLock.readLock()).lock();
verify(clientHooksLock.readLock()).unlock();
api.getHooks();
verify(apiLock.readLock()).lock();
verify(apiLock.readLock()).unlock();
}
@Test
void setContextShouldWriteLockAndUnlock() {
client.setEvaluationContext(new ImmutableContext());
verify(clientContextLock.writeLock()).lock();
verify(clientContextLock.writeLock()).unlock();
api.setEvaluationContext(new ImmutableContext());
verify(apiLock.writeLock()).lock();
verify(apiLock.writeLock()).unlock();
}
@Test
void getContextShouldReadLockAndUnlock() {
client.getEvaluationContext();
verify(clientContextLock.readLock()).lock();
verify(clientContextLock.readLock()).unlock();
api.getEvaluationContext();
verify(apiLock.readLock()).lock();
verify(apiLock.readLock()).unlock();
}
@Test
void setTransactionalContextPropagatorShouldWriteLockAndUnlock() {
api.setTransactionContextPropagator(new NoOpTransactionContextPropagator());
@ -195,13 +147,6 @@ class LockingTest {
verify(apiLock.readLock()).unlock();
}
@Test
void clearHooksShouldWriteLockAndUnlock() {
api.clearHooks();
verify(apiLock.writeLock()).lock();
verify(apiLock.writeLock()).unlock();
}
private static ReentrantReadWriteLock.ReadLock mockInnerReadLock() {
ReentrantReadWriteLock.ReadLock readLockMock = mock(ReentrantReadWriteLock.ReadLock.class);
doNothing().when(readLockMock).lock();

View File

@ -3,6 +3,7 @@ package dev.openfeature.sdk;
import static dev.openfeature.sdk.EvaluationContext.TARGETING_KEY;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collections;
@ -137,4 +138,31 @@ class MutableContextTest {
assertEquals(2, context.getValue("key2").asInteger());
assertEquals(3.0, context.getValue("key3").asDouble());
}
@DisplayName("Two different MutableContext objects with the different contents are not considered equal")
@Test
void unequalMutableContextsAreNotEqual() {
final Map<String, Value> attributes = new HashMap<>();
attributes.put("key1", new Value("val1"));
final MutableContext ctx = new MutableContext(attributes);
final Map<String, Value> attributes2 = new HashMap<>();
final MutableContext ctx2 = new MutableContext(attributes2);
assertNotEquals(ctx, ctx2);
}
@DisplayName("Two different MutableContext objects with the same content are considered equal")
@Test
void equalMutableContextsAreEqual() {
final Map<String, Value> attributes = new HashMap<>();
attributes.put("key1", new Value("val1"));
final MutableContext ctx = new MutableContext(attributes);
final Map<String, Value> attributes2 = new HashMap<>();
attributes2.put("key1", new Value("val1"));
final MutableContext ctx2 = new MutableContext(attributes2);
assertEquals(ctx, ctx2);
}
}

View File

@ -0,0 +1,67 @@
package dev.openfeature.sdk;
import static org.junit.jupiter.api.Assertions.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
class MutableStructureTest {
@Test
void emptyMutableStructureIsEmpty() {
MutableStructure m1 = new MutableStructure();
assertTrue(m1.isEmpty());
}
@Test
void mutableStructureWithNullBackingStructureIsEmpty() {
MutableStructure m1 = new MutableStructure(null);
assertTrue(m1.isEmpty());
}
@Test
void unequalMutableStructuresAreNotEqual() {
MutableStructure m1 = new MutableStructure();
m1.add("key1", "val1");
MutableStructure m2 = new MutableStructure();
m2.add("key2", "val2");
assertNotEquals(m1, m2);
}
@Test
void equalMutableStructuresAreEqual() {
MutableStructure m1 = new MutableStructure();
m1.add("key1", "val1");
MutableStructure m2 = new MutableStructure();
m2.add("key1", "val1");
assertEquals(m1, m2);
}
@Test
void equalAbstractStructuresOfDifferentTypesAreNotEqual() {
MutableStructure m1 = new MutableStructure();
m1.add("key1", "val1");
HashMap<String, Value> map = new HashMap<>();
map.put("key1", new Value("val1"));
AbstractStructure m2 = new AbstractStructure(map) {
@Override
public Set<String> keySet() {
return attributes.keySet();
}
@Override
public Value getValue(String key) {
return attributes.get(key);
}
@Override
public Map<String, Value> asMap() {
return attributes;
}
};
assertNotEquals(m1, m2);
}
}

View File

@ -0,0 +1,17 @@
package dev.openfeature.sdk;
import static org.junit.jupiter.api.Assertions.assertSame;
import org.junit.jupiter.api.Test;
class OpenFeatureAPISingeltonTest {
@Specification(
number = "1.1.1",
text =
"The API, and any state it maintains SHOULD exist as a global singleton, even in cases wherein multiple versions of the API are present at runtime.")
@Test
void global_singleton() {
assertSame(OpenFeatureAPI.getInstance(), OpenFeatureAPI.getInstance());
}
}

View File

@ -5,10 +5,10 @@ import static org.assertj.core.api.Assertions.assertThatCode;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import dev.openfeature.sdk.providers.memory.InMemoryProvider;
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import dev.openfeature.sdk.testutils.TestEventsProvider;
import java.util.Collections;
import java.util.HashMap;
@ -23,13 +23,13 @@ class OpenFeatureAPITest {
@BeforeEach
void setupTest() {
api = OpenFeatureAPI.getInstance();
api = new OpenFeatureAPI();
}
@Test
void namedProviderTest() {
FeatureProvider provider = new NoOpProvider();
FeatureProviderTestUtils.setFeatureProvider("namedProviderTest", provider);
api.setProviderAndWait("namedProviderTest", provider);
assertThat(provider.getMetadata().getName())
.isEqualTo(api.getProviderMetadata("namedProviderTest").getName());
@ -44,14 +44,10 @@ class OpenFeatureAPITest {
String domain = "namedProviderOverwrittenTest";
FeatureProvider provider1 = new NoOpProvider();
FeatureProvider provider2 = new DoSomethingProvider();
FeatureProviderTestUtils.setFeatureProvider(domain, provider1);
FeatureProviderTestUtils.setFeatureProvider(domain, provider2);
api.setProviderAndWait(domain, provider1);
api.setProviderAndWait(domain, provider2);
assertThat(OpenFeatureAPI.getInstance()
.getProvider(domain)
.getMetadata()
.getName())
.isEqualTo(DoSomethingProvider.name);
assertThat(api.getProvider(domain).getMetadata().getName()).isEqualTo(DoSomethingProvider.name);
}
@Test
@ -60,17 +56,17 @@ class OpenFeatureAPITest {
FeatureProvider noOpAsNonEventingProvider = new NoOpProvider();
// register same provider for multiple names & as default provider
OpenFeatureAPI.getInstance().setProviderAndWait(inMemAsEventingProvider);
OpenFeatureAPI.getInstance().setProviderAndWait("clientA", inMemAsEventingProvider);
OpenFeatureAPI.getInstance().setProviderAndWait("clientB", inMemAsEventingProvider);
OpenFeatureAPI.getInstance().setProviderAndWait("clientC", noOpAsNonEventingProvider);
OpenFeatureAPI.getInstance().setProviderAndWait("clientD", noOpAsNonEventingProvider);
api.setProviderAndWait(inMemAsEventingProvider);
api.setProviderAndWait("clientA", inMemAsEventingProvider);
api.setProviderAndWait("clientB", inMemAsEventingProvider);
api.setProviderAndWait("clientC", noOpAsNonEventingProvider);
api.setProviderAndWait("clientD", noOpAsNonEventingProvider);
assertEquals(inMemAsEventingProvider, OpenFeatureAPI.getInstance().getProvider());
assertEquals(inMemAsEventingProvider, OpenFeatureAPI.getInstance().getProvider("clientA"));
assertEquals(inMemAsEventingProvider, OpenFeatureAPI.getInstance().getProvider("clientB"));
assertEquals(noOpAsNonEventingProvider, OpenFeatureAPI.getInstance().getProvider("clientC"));
assertEquals(noOpAsNonEventingProvider, OpenFeatureAPI.getInstance().getProvider("clientD"));
assertEquals(inMemAsEventingProvider, api.getProvider());
assertEquals(inMemAsEventingProvider, api.getProvider("clientA"));
assertEquals(inMemAsEventingProvider, api.getProvider("clientB"));
assertEquals(noOpAsNonEventingProvider, api.getProvider("clientC"));
assertEquals(noOpAsNonEventingProvider, api.getProvider("clientD"));
}
@Test
@ -101,26 +97,23 @@ class OpenFeatureAPITest {
String domain = "namedProviderOverwrittenTest";
FeatureProvider provider1 = new NoOpProvider();
FeatureProvider provider2 = new TestEventsProvider();
FeatureProviderTestUtils.setFeatureProvider(domain, provider1);
FeatureProviderTestUtils.setFeatureProvider(domain, provider2);
api.setProviderAndWait(domain, provider1);
api.setProviderAndWait(domain, provider2);
provider2.initialize(null);
assertThat(OpenFeatureAPI.getInstance().getClient(domain).getProviderState())
.isEqualTo(ProviderState.READY);
assertThat(api.getClient(domain).getProviderState()).isEqualTo(ProviderState.READY);
}
@Test
void featureProviderTrackIsCalled() throws Exception {
FeatureProvider featureProvider = mock(FeatureProvider.class);
FeatureProviderTestUtils.setFeatureProvider(featureProvider);
api.setProviderAndWait(featureProvider);
OpenFeatureAPI.getInstance()
.getClient()
.track("track-event", new ImmutableContext(), new MutableTrackingEventDetails(22.2f));
api.getClient().track("track-event", new ImmutableContext(), new MutableTrackingEventDetails(22.2f));
verify(featureProvider).initialize(any());
verify(featureProvider).getMetadata();
verify(featureProvider, times(2)).getMetadata();
verify(featureProvider).track(any(), any(), any());
}
}

View File

@ -0,0 +1,10 @@
package dev.openfeature.sdk;
public class OpenFeatureAPITestUtil {
private OpenFeatureAPITestUtil() {}
public static OpenFeatureAPI createAPI() {
return new OpenFeatureAPI();
}
}

View File

@ -38,7 +38,7 @@ class OpenFeatureClientTest implements HookFixtures {
@Test
@DisplayName("should not throw exception if hook has different type argument than hookContext")
void shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext() {
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
OpenFeatureAPI api = new OpenFeatureAPI();
api.setProviderAndWait(
"shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext", new DoSomethingProvider());
Client client = api.getClient("shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext");
@ -82,7 +82,7 @@ class OpenFeatureClientTest implements HookFixtures {
@DisplayName("Should not call evaluation methods when the provider has state FATAL")
void shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState() {
FeatureProvider provider = new TestEventsProvider(100, true, "fake fatal", true);
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
OpenFeatureAPI api = new OpenFeatureAPI();
Client client = api.getClient("shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState");
assertThrows(
@ -97,7 +97,7 @@ class OpenFeatureClientTest implements HookFixtures {
@DisplayName("Should not call evaluation methods when the provider has state NOT_READY")
void shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState() {
FeatureProvider provider = new TestEventsProvider(5000);
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
OpenFeatureAPI api = new OpenFeatureAPI();
api.setProvider("shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState", provider);
Client client = api.getClient("shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState");
FlagEvaluationDetails<Boolean> details = client.getBooleanDetails("key", true);

View File

@ -35,7 +35,7 @@ class ProviderRepositoryTest {
@BeforeEach
void setupTest() {
providerRepository = new ProviderRepository();
providerRepository = new ProviderRepository(new OpenFeatureAPI());
}
@Nested

View File

@ -1,6 +1,5 @@
package dev.openfeature.sdk;
import static dev.openfeature.sdk.testutils.FeatureProviderTestUtils.setFeatureProvider;
import static org.mockito.Mockito.*;
import dev.openfeature.sdk.fixtures.ProviderFixture;
@ -15,9 +14,19 @@ import org.junit.jupiter.api.Test;
class ShutdownBehaviorSpecTest {
private String DOMAIN = "myDomain";
private OpenFeatureAPI api;
void setFeatureProvider(FeatureProvider featureProvider) {
api.setProviderAndWait(featureProvider);
}
void setFeatureProvider(String domain, FeatureProvider featureProvider) {
api.setProviderAndWait(domain, featureProvider);
}
@BeforeEach
void resetFeatureProvider() {
api = new OpenFeatureAPI();
setFeatureProvider(new NoOpProvider());
}
@ -110,7 +119,6 @@ class ShutdownBehaviorSpecTest {
FeatureProvider namedProvider = ProviderFixture.createMockedProvider();
setFeatureProvider(defaultProvider);
setFeatureProvider(DOMAIN, namedProvider);
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
synchronized (OpenFeatureAPI.class) {
api.shutdown();
@ -125,15 +133,14 @@ class ShutdownBehaviorSpecTest {
@Test
@DisplayName("once shutdown is complete, api must be ready to use again")
void apiIsReadyToUseAfterShutdown() {
final OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance();
NoOpProvider p1 = new NoOpProvider();
openFeatureAPI.setProvider(p1);
api.setProvider(p1);
openFeatureAPI.shutdown();
api.shutdown();
NoOpProvider p2 = new NoOpProvider();
openFeatureAPI.setProvider(p2);
api.setProvider(p2);
}
}
}

View File

@ -0,0 +1,231 @@
package dev.openfeature.sdk;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
public class TelemetryTest {
@Test
void testCreatesEvaluationEventWithMandatoryFields() {
// Arrange
String flagKey = "test-flag";
String providerName = "test-provider";
String reason = "static";
Metadata providerMetadata = mock(Metadata.class);
when(providerMetadata.getName()).thenReturn(providerName);
HookContext<Boolean> hookContext = HookContext.<Boolean>builder()
.flagKey(flagKey)
.providerMetadata(providerMetadata)
.type(FlagValueType.BOOLEAN)
.defaultValue(false)
.ctx(new ImmutableContext())
.build();
FlagEvaluationDetails<Boolean> evaluation = FlagEvaluationDetails.<Boolean>builder()
.reason(reason)
.value(true)
.build();
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, evaluation);
assertEquals(Telemetry.FLAG_EVALUATION_EVENT_NAME, event.getName());
assertEquals(flagKey, event.getAttributes().get(Telemetry.TELEMETRY_KEY));
assertEquals(providerName, event.getAttributes().get(Telemetry.TELEMETRY_PROVIDER));
assertEquals(reason.toLowerCase(), event.getAttributes().get(Telemetry.TELEMETRY_REASON));
}
@Test
void testHandlesNullReason() {
// Arrange
String flagKey = "test-flag";
String providerName = "test-provider";
Metadata providerMetadata = mock(Metadata.class);
when(providerMetadata.getName()).thenReturn(providerName);
HookContext<Boolean> hookContext = HookContext.<Boolean>builder()
.flagKey(flagKey)
.providerMetadata(providerMetadata)
.type(FlagValueType.BOOLEAN)
.defaultValue(false)
.ctx(new ImmutableContext())
.build();
FlagEvaluationDetails<Boolean> evaluation = FlagEvaluationDetails.<Boolean>builder()
.reason(null)
.value(true)
.build();
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, evaluation);
assertEquals(Reason.UNKNOWN.name().toLowerCase(), event.getAttributes().get(Telemetry.TELEMETRY_REASON));
}
@Test
void testSetsVariantAttributeWhenVariantExists() {
HookContext<String> hookContext = HookContext.<String>builder()
.flagKey("testFlag")
.type(FlagValueType.STRING)
.defaultValue("default")
.ctx(mock(EvaluationContext.class))
.clientMetadata(mock(ClientMetadata.class))
.providerMetadata(mock(Metadata.class))
.build();
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
.variant("testVariant")
.flagMetadata(ImmutableMetadata.builder().build())
.build();
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
assertEquals("testVariant", event.getAttributes().get(Telemetry.TELEMETRY_VARIANT));
}
@Test
void test_sets_value_in_body_when_variant_is_null() {
HookContext<String> hookContext = HookContext.<String>builder()
.flagKey("testFlag")
.type(FlagValueType.STRING)
.defaultValue("default")
.ctx(mock(EvaluationContext.class))
.clientMetadata(mock(ClientMetadata.class))
.providerMetadata(mock(Metadata.class))
.build();
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
.value("testValue")
.flagMetadata(ImmutableMetadata.builder().build())
.build();
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
assertEquals("testValue", event.getAttributes().get(Telemetry.TELEMETRY_VALUE));
}
@Test
void testAllFieldsPopulated() {
EvaluationContext evaluationContext = mock(EvaluationContext.class);
when(evaluationContext.getTargetingKey()).thenReturn("realTargetingKey");
Metadata providerMetadata = mock(Metadata.class);
when(providerMetadata.getName()).thenReturn("realProviderName");
HookContext<String> hookContext = HookContext.<String>builder()
.flagKey("realFlag")
.type(FlagValueType.STRING)
.defaultValue("realDefault")
.ctx(evaluationContext)
.clientMetadata(mock(ClientMetadata.class))
.providerMetadata(providerMetadata)
.build();
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
.flagMetadata(ImmutableMetadata.builder()
.addString("contextId", "realContextId")
.addString("flagSetId", "realFlagSetId")
.addString("version", "realVersion")
.build())
.reason(Reason.DEFAULT.name())
.variant("realVariant")
.build();
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
assertEquals("realFlag", event.getAttributes().get(Telemetry.TELEMETRY_KEY));
assertEquals("realProviderName", event.getAttributes().get(Telemetry.TELEMETRY_PROVIDER));
assertEquals("default", event.getAttributes().get(Telemetry.TELEMETRY_REASON));
assertEquals("realContextId", event.getAttributes().get(Telemetry.TELEMETRY_CONTEXT_ID));
assertEquals("realFlagSetId", event.getAttributes().get(Telemetry.TELEMETRY_FLAG_SET_ID));
assertEquals("realVersion", event.getAttributes().get(Telemetry.TELEMETRY_VERSION));
assertNull(event.getAttributes().get(Telemetry.TELEMETRY_ERROR_CODE));
assertEquals("realVariant", event.getAttributes().get(Telemetry.TELEMETRY_VARIANT));
}
@Test
void testErrorEvaluation() {
EvaluationContext evaluationContext = mock(EvaluationContext.class);
when(evaluationContext.getTargetingKey()).thenReturn("realTargetingKey");
Metadata providerMetadata = mock(Metadata.class);
when(providerMetadata.getName()).thenReturn("realProviderName");
HookContext<String> hookContext = HookContext.<String>builder()
.flagKey("realFlag")
.type(FlagValueType.STRING)
.defaultValue("realDefault")
.ctx(evaluationContext)
.clientMetadata(mock(ClientMetadata.class))
.providerMetadata(providerMetadata)
.build();
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
.flagMetadata(ImmutableMetadata.builder()
.addString("contextId", "realContextId")
.addString("flagSetId", "realFlagSetId")
.addString("version", "realVersion")
.build())
.reason(Reason.ERROR.name())
.errorMessage("realErrorMessage")
.build();
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
assertEquals("realFlag", event.getAttributes().get(Telemetry.TELEMETRY_KEY));
assertEquals("realProviderName", event.getAttributes().get(Telemetry.TELEMETRY_PROVIDER));
assertEquals("error", event.getAttributes().get(Telemetry.TELEMETRY_REASON));
assertEquals("realContextId", event.getAttributes().get(Telemetry.TELEMETRY_CONTEXT_ID));
assertEquals("realFlagSetId", event.getAttributes().get(Telemetry.TELEMETRY_FLAG_SET_ID));
assertEquals("realVersion", event.getAttributes().get(Telemetry.TELEMETRY_VERSION));
assertEquals(ErrorCode.GENERAL, event.getAttributes().get(Telemetry.TELEMETRY_ERROR_CODE));
assertEquals("realErrorMessage", event.getAttributes().get(Telemetry.TELEMETRY_ERROR_MSG));
assertNull(event.getAttributes().get(Telemetry.TELEMETRY_VARIANT));
}
@Test
void testErrorCodeEvaluation() {
EvaluationContext evaluationContext = mock(EvaluationContext.class);
when(evaluationContext.getTargetingKey()).thenReturn("realTargetingKey");
Metadata providerMetadata = mock(Metadata.class);
when(providerMetadata.getName()).thenReturn("realProviderName");
HookContext<String> hookContext = HookContext.<String>builder()
.flagKey("realFlag")
.type(FlagValueType.STRING)
.defaultValue("realDefault")
.ctx(evaluationContext)
.clientMetadata(mock(ClientMetadata.class))
.providerMetadata(providerMetadata)
.build();
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
.flagMetadata(ImmutableMetadata.builder()
.addString("contextId", "realContextId")
.addString("flagSetId", "realFlagSetId")
.addString("version", "realVersion")
.build())
.reason(Reason.ERROR.name())
.errorMessage("realErrorMessage")
.errorCode(ErrorCode.INVALID_CONTEXT)
.build();
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
assertEquals("realFlag", event.getAttributes().get(Telemetry.TELEMETRY_KEY));
assertEquals("realProviderName", event.getAttributes().get(Telemetry.TELEMETRY_PROVIDER));
assertEquals("error", event.getAttributes().get(Telemetry.TELEMETRY_REASON));
assertEquals("realContextId", event.getAttributes().get(Telemetry.TELEMETRY_CONTEXT_ID));
assertEquals("realFlagSetId", event.getAttributes().get(Telemetry.TELEMETRY_FLAG_SET_ID));
assertEquals("realVersion", event.getAttributes().get(Telemetry.TELEMETRY_VERSION));
assertEquals(ErrorCode.INVALID_CONTEXT, event.getAttributes().get(Telemetry.TELEMETRY_ERROR_CODE));
assertEquals("realErrorMessage", event.getAttributes().get(Telemetry.TELEMETRY_ERROR_MSG));
assertNull(event.getAttributes().get(Telemetry.TELEMETRY_VARIANT));
}
}

View File

@ -15,7 +15,6 @@ import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import dev.openfeature.sdk.fixtures.ProviderFixture;
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import java.util.HashMap;
import java.util.Map;
import lombok.SneakyThrows;
@ -29,7 +28,7 @@ class TrackingSpecTest {
@BeforeEach
void getApiInstance() {
api = OpenFeatureAPI.getInstance();
api = new OpenFeatureAPI();
client = api.getClient();
}
@ -116,7 +115,7 @@ class TrackingSpecTest {
client.setEvaluationContext(clCtx);
FeatureProvider provider = ProviderFixture.createMockedProvider();
FeatureProviderTestUtils.setFeatureProvider(provider);
api.setProviderAndWait(provider);
client.track("event", new MutableContext().add("my-key", "final"), new MutableTrackingEventDetails(0.0f));
@ -170,8 +169,7 @@ class TrackingSpecTest {
.add("my-struct", new Value(new MutableTrackingEventDetails()));
assertEquals(expectedMap, details.asMap());
assertThatCode(() -> OpenFeatureAPI.getInstance()
.getClient()
assertThatCode(() -> api.getClient()
.track("tracking-event-name", new ImmutableContext(), new MutableTrackingEventDetails()))
.doesNotThrowAnyException();
@ -188,8 +186,7 @@ class TrackingSpecTest {
ImmutableTrackingEventDetails immutableDetails = new ImmutableTrackingEventDetails(2, expectedMap);
assertEquals(expectedImmutable, immutableDetails.asMap());
assertThatCode(() -> OpenFeatureAPI.getInstance()
.getClient()
assertThatCode(() -> api.getClient()
.track("tracking-event-name", new ImmutableContext(), new ImmutableTrackingEventDetails()))
.doesNotThrowAnyException();
}

View File

@ -2,6 +2,7 @@ package dev.openfeature.sdk;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -11,15 +12,15 @@ import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
public class ValueTest {
class ValueTest {
@Test
public void noArgShouldContainNull() {
void noArgShouldContainNull() {
Value value = new Value();
assertTrue(value.isNull());
}
@Test
public void objectArgShouldContainObject() {
void objectArgShouldContainObject() {
try {
// int is a special case, see intObjectArgShouldConvertToInt()
List<Object> list = new ArrayList<>();
@ -42,7 +43,7 @@ public class ValueTest {
}
@Test
public void intObjectArgShouldConvertToInt() {
void intObjectArgShouldConvertToInt() {
try {
Object innerValue = 1;
Value value = new Value(innerValue);
@ -53,7 +54,7 @@ public class ValueTest {
}
@Test
public void invalidObjectArgShouldThrow() {
void invalidObjectArgShouldThrow() {
class Something {}
@ -63,7 +64,7 @@ public class ValueTest {
}
@Test
public void boolArgShouldContainBool() {
void boolArgShouldContainBool() {
boolean innerValue = true;
Value value = new Value(innerValue);
assertTrue(value.isBoolean());
@ -71,7 +72,7 @@ public class ValueTest {
}
@Test
public void numericArgShouldReturnDoubleOrInt() {
void numericArgShouldReturnDoubleOrInt() {
double innerDoubleValue = 1.75;
Value doubleValue = new Value(innerDoubleValue);
assertTrue(doubleValue.isNumber());
@ -86,7 +87,7 @@ public class ValueTest {
}
@Test
public void stringArgShouldContainString() {
void stringArgShouldContainString() {
String innerValue = "hi!";
Value value = new Value(innerValue);
assertTrue(value.isString());
@ -94,7 +95,7 @@ public class ValueTest {
}
@Test
public void dateShouldContainDate() {
void dateShouldContainDate() {
Instant innerValue = Instant.now();
Value value = new Value(innerValue);
assertTrue(value.isInstant());
@ -102,7 +103,7 @@ public class ValueTest {
}
@Test
public void structureShouldContainStructure() {
void structureShouldContainStructure() {
String INNER_KEY = "key";
String INNER_VALUE = "val";
MutableStructure innerValue = new MutableStructure().add(INNER_KEY, INNER_VALUE);
@ -112,7 +113,7 @@ public class ValueTest {
}
@Test
public void listArgShouldContainList() {
void listArgShouldContainList() {
String ITEM_VALUE = "val";
List<Value> innerValue = new ArrayList<Value>();
innerValue.add(new Value(ITEM_VALUE));
@ -122,7 +123,7 @@ public class ValueTest {
}
@Test
public void listMustBeOfValues() {
void listMustBeOfValues() {
String item = "item";
List<String> list = new ArrayList<>();
list.add(item);
@ -135,7 +136,7 @@ public class ValueTest {
}
@Test
public void emptyListAllowed() {
void emptyListAllowed() {
List<String> list = new ArrayList<>();
try {
Value value = new Value((Object) list);
@ -148,7 +149,7 @@ public class ValueTest {
}
@Test
public void valueConstructorValidateListInternals() {
void valueConstructorValidateListInternals() {
List<Object> list = new ArrayList<>();
list.add(new Value("item"));
list.add("item");
@ -157,8 +158,22 @@ public class ValueTest {
}
@Test
public void noOpFinalize() {
void noOpFinalize() {
Value val = new Value();
assertDoesNotThrow(val::finalize); // does nothing, but we want to defined in and make it final.
}
@Test
void equalValuesShouldBeEqual() {
Value val1 = new Value(12312312);
Value val2 = new Value(12312312);
assertEquals(val1, val2);
}
@Test
void unequalValuesShouldNotBeEqual() {
Value val1 = new Value("a");
Value val2 = new Value("b");
assertNotEquals(val1, val2);
}
}

View File

@ -0,0 +1,27 @@
package dev.openfeature.sdk.arch;
import static com.tngtech.archunit.base.DescribedPredicate.describe;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
@AnalyzeClasses(packages = "dev.openfeature.sdk")
public class ArchitectureTest {
@ArchTest
public static final ArchRule avoidGetInstances = noClasses()
.that()
.resideOutsideOfPackages("..benchmark", "..e2e.*")
.and()
.haveSimpleNameNotEndingWith("SingeltonTest")
.should()
.callMethodWhere(describe(
"Avoid Internal usage of OpenFeatureAPI.GetInstances",
// Target method may not reside in class annotated with BusinessException
methodCall ->
methodCall.getTarget().getOwner().getFullName().equals("dev.openfeature.sdk.OpenFeatureAPI")
// And target method may not have the static modifier
&& methodCall.getTarget().getName().equals("getInstance")));
}

View File

@ -0,0 +1,48 @@
package dev.openfeature.sdk.e2e;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.FeatureProvider;
import dev.openfeature.sdk.Metadata;
import dev.openfeature.sdk.ProviderEvaluation;
import dev.openfeature.sdk.Value;
import lombok.Getter;
@Getter
public class ContextStoringProvider implements FeatureProvider {
private EvaluationContext evaluationContext;
@Override
public Metadata getMetadata() {
return () -> getClass().getSimpleName();
}
@Override
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}
@Override
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}
@Override
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}
@Override
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}
@Override
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}
}

View File

@ -1,16 +1,18 @@
package dev.openfeature.sdk.e2e;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.SelectDirectories;
import org.junit.platform.suite.api.Suite;
@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features/evaluation.feature")
@SelectDirectories("spec/specification/assets/gherkin")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.sdk.e2e.evaluation")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.sdk.e2e.steps")
@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory")
public class EvaluationTest {}

View File

@ -0,0 +1,13 @@
package dev.openfeature.sdk.e2e;
public class Flag {
public String name;
public Object defaultValue;
public String type;
public Flag(String type, String name, Object defaultValue) {
this.name = name;
this.defaultValue = defaultValue;
this.type = type;
}
}

View File

@ -0,0 +1,50 @@
package dev.openfeature.sdk.e2e;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.Hook;
import dev.openfeature.sdk.HookContext;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import lombok.Getter;
public class MockHook implements Hook {
@Getter
private boolean beforeCalled;
@Getter
private boolean afterCalled;
@Getter
private boolean errorCalled;
@Getter
private boolean finallyAfterCalled;
@Getter
private final Map<String, FlagEvaluationDetails> evaluationDetails = new HashMap<>();
@Override
public Optional<EvaluationContext> before(HookContext ctx, Map hints) {
beforeCalled = true;
return Optional.of(ctx.getCtx());
}
@Override
public void after(HookContext ctx, FlagEvaluationDetails details, Map hints) {
afterCalled = true;
evaluationDetails.put("after", details);
}
@Override
public void error(HookContext ctx, Exception error, Map hints) {
errorCalled = true;
}
@Override
public void finallyAfter(HookContext ctx, FlagEvaluationDetails details, Map hints) {
finallyAfterCalled = true;
evaluationDetails.put("finally", details);
}
}

View File

@ -0,0 +1,19 @@
package dev.openfeature.sdk.e2e;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.FeatureProvider;
import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.MutableContext;
import java.util.List;
public class State {
public Client client;
public Flag flag;
public MutableContext context = new MutableContext();
public FlagEvaluationDetails evaluation;
public MockHook hook;
public FeatureProvider provider;
public EvaluationContext invocationContext;
public List<String> levels;
}

View File

@ -0,0 +1,28 @@
package dev.openfeature.sdk.e2e;
import java.util.Objects;
public final class Utils {
private Utils() {}
public static Object convert(String value, String type) {
if (Objects.equals(value, "null")) {
return null;
}
switch (type.toLowerCase()) {
case "boolean":
return Boolean.parseBoolean(value);
case "string":
return value;
case "integer":
return Integer.parseInt(value);
case "float":
case "double":
return Double.parseDouble(value);
case "long":
return Long.parseLong(value);
}
throw new RuntimeException("Unknown config type: " + type);
}
}

View File

@ -0,0 +1,104 @@
package dev.openfeature.sdk.e2e.steps;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.Hook;
import dev.openfeature.sdk.HookContext;
import dev.openfeature.sdk.ImmutableContext;
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.ThreadLocalTransactionContextPropagator;
import dev.openfeature.sdk.Value;
import dev.openfeature.sdk.e2e.ContextStoringProvider;
import dev.openfeature.sdk.e2e.State;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class ContextSteps {
private final State state;
public ContextSteps(State state) {
this.state = state;
}
@Given("a stable provider with retrievable context is registered")
public void setup() {
ContextStoringProvider provider = new ContextStoringProvider();
state.provider = provider;
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
state.client = OpenFeatureAPI.getInstance().getClient();
OpenFeatureAPI.getInstance().setTransactionContextPropagator(new ThreadLocalTransactionContextPropagator());
}
@When("A context entry with key {string} and value {string} is added to the {string} level")
public void aContextWithKeyAndValueIsAddedToTheLevel(String contextKey, String contextValue, String level) {
addContextEntry(contextKey, contextValue, level);
}
private void addContextEntry(String contextKey, String contextValue, String level) {
Map<String, Value> data = new HashMap<>();
data.put(contextKey, new Value(contextValue));
EvaluationContext context = new ImmutableContext(data);
if ("API".equals(level)) {
OpenFeatureAPI.getInstance().setEvaluationContext(context);
} else if ("Transaction".equals(level)) {
OpenFeatureAPI.getInstance().setTransactionContext(context);
} else if ("Client".equals(level)) {
state.client.setEvaluationContext(context);
} else if ("Invocation".equals(level)) {
state.invocationContext = context;
} else if ("Before Hooks".equals(level)) {
state.client.addHooks(new Hook() {
@Override
public Optional<EvaluationContext> before(HookContext ctx, Map hints) {
return Optional.of(context);
}
});
} else {
throw new IllegalArgumentException("Unknown level: " + level);
}
}
@When("Some flag was evaluated")
public void someFlagWasEvaluated() {
state.evaluation = state.client.getStringDetails("unused", "unused", state.invocationContext);
}
@Then("The merged context contains an entry with key {string} and value {string}")
public void theMergedContextContainsAnEntryWithKeyAndValue(String contextKey, String contextValue) {
assertInstanceOf(
ContextStoringProvider.class,
state.provider,
"In order to use this step, you need to set a ContextStoringProvider");
EvaluationContext ctx = ((ContextStoringProvider) state.provider).getEvaluationContext();
assertNotNull(ctx);
assertNotNull(ctx.getValue(contextKey));
assertNotNull(ctx.getValue(contextKey).asString());
assertEquals(contextValue, ctx.getValue(contextKey).asString());
}
@Given("A table with levels of increasing precedence")
public void aTableWithLevelsOfIncreasingPrecedence(DataTable levelsTable) {
state.levels = levelsTable.asList();
}
@And(
"Context entries for each level from API level down to the {string} level, with key {string} and value {string}")
public void contextEntriesForEachLevelFromAPILevelDownToTheLevelWithKeyAndValue(
String maxLevel, String key, String value) {
for (String level : state.levels) {
addContextEntry(key, value, level);
if (level.equals(maxLevel)) {
return;
}
}
}
}

View File

@ -0,0 +1,104 @@
package dev.openfeature.sdk.e2e.steps;
import static org.assertj.core.api.Assertions.assertThat;
import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.ImmutableMetadata;
import dev.openfeature.sdk.Value;
import dev.openfeature.sdk.e2e.Flag;
import dev.openfeature.sdk.e2e.State;
import dev.openfeature.sdk.e2e.Utils;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
public class FlagStepDefinitions {
private final State state;
public FlagStepDefinitions(State state) {
this.state = state;
}
@Given("a {}-flag with key {string} and a default value {string}")
public void givenAFlag(String type, String name, String defaultValue) {
state.flag = new Flag(type, name, Utils.convert(defaultValue, type));
}
@When("the flag was evaluated with details")
public void the_flag_was_evaluated_with_details() {
FlagEvaluationDetails details;
switch (state.flag.type.toLowerCase()) {
case "string":
details =
state.client.getStringDetails(state.flag.name, (String) state.flag.defaultValue, state.context);
break;
case "boolean":
details = state.client.getBooleanDetails(
state.flag.name, (Boolean) state.flag.defaultValue, state.context);
break;
case "float":
details =
state.client.getDoubleDetails(state.flag.name, (Double) state.flag.defaultValue, state.context);
break;
case "integer":
details = state.client.getIntegerDetails(
state.flag.name, (Integer) state.flag.defaultValue, state.context);
break;
case "object":
details =
state.client.getObjectDetails(state.flag.name, (Value) state.flag.defaultValue, state.context);
break;
default:
throw new AssertionError();
}
state.evaluation = details;
}
@Then("the resolved details value should be {string}")
public void the_resolved_details_value_should_be(String value) {
assertThat(state.evaluation.getValue()).isEqualTo(Utils.convert(value, state.flag.type));
}
@Then("the reason should be {string}")
public void the_reason_should_be(String reason) {
assertThat(state.evaluation.getReason()).isEqualTo(reason);
}
@Then("the variant should be {string}")
public void the_variant_should_be(String variant) {
assertThat(state.evaluation.getVariant()).isEqualTo(variant);
}
@Then("the resolved metadata value \"{}\" with type \"{}\" should be \"{}\"")
public void theResolvedMetadataValueShouldBe(String key, String type, String value)
throws NoSuchFieldException, IllegalAccessException {
Field f = state.evaluation.getFlagMetadata().getClass().getDeclaredField("metadata");
f.setAccessible(true);
HashMap<String, Object> metadata = (HashMap<String, Object>) f.get(state.evaluation.getFlagMetadata());
assertThat(metadata).containsEntry(key, Utils.convert(value, type));
}
@Then("the resolved metadata is empty")
public void theResolvedMetadataIsEmpty() {
assertThat(state.evaluation.getFlagMetadata().isEmpty()).isTrue();
}
@Then("the resolved metadata should contain")
public void theResolvedMetadataShouldContain(DataTable dataTable) {
ImmutableMetadata evaluationMetadata = state.evaluation.getFlagMetadata();
List<List<String>> asLists = dataTable.asLists();
for (int i = 1; i < asLists.size(); i++) { // skip the header of the table
List<String> line = asLists.get(i);
String key = line.get(0);
String metadataType = line.get(1);
Object value = Utils.convert(line.get(2), metadataType);
assertThat(value).isNotNull();
assertThat(evaluationMetadata.getValue(key, value.getClass())).isEqualTo(value);
}
}
}

View File

@ -0,0 +1,84 @@
package dev.openfeature.sdk.e2e.steps;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.e2e.MockHook;
import dev.openfeature.sdk.e2e.State;
import dev.openfeature.sdk.e2e.Utils;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import java.util.List;
import java.util.Map;
public class HookSteps {
private final State state;
public HookSteps(State state) {
this.state = state;
}
@Given("a client with added hook")
public void aClientWithAddedHook() {
MockHook hook = new MockHook();
state.hook = hook;
state.client.addHooks(hook);
}
@Then("the {string} hook should have been executed")
public void theHookShouldHaveBeenExecuted(String hookName) {
assertHookCalled(hookName);
}
public void assertHookCalled(String hookName) {
if ("before".equals(hookName)) {
assertTrue(state.hook.isBeforeCalled());
} else if ("after".equals(hookName)) {
assertTrue(state.hook.isAfterCalled());
} else if ("error".equals(hookName)) {
assertTrue(state.hook.isErrorCalled());
} else if ("finally".equals(hookName)) {
assertTrue(state.hook.isFinallyAfterCalled());
} else {
throw new IllegalArgumentException(hookName + " is not a valid hook name");
}
}
@And("the {string} hooks should be called with evaluation details")
public void theHooksShouldBeCalledWithEvaluationDetails(String hookNames, DataTable data) {
for (String hookName : hookNames.split(", ")) {
assertHookCalled(hookName);
FlagEvaluationDetails evaluationDetails =
state.hook.getEvaluationDetails().get(hookName);
assertNotNull(evaluationDetails);
List<Map<String, String>> dataEntries = data.asMaps();
for (Map<String, String> line : dataEntries) {
String key = line.get("key");
Object expected = Utils.convert(line.get("value"), line.get("data_type"));
Object actual;
if ("flag_key".equals(key)) {
actual = evaluationDetails.getFlagKey();
} else if ("value".equals(key)) {
actual = evaluationDetails.getValue();
} else if ("variant".equals(key)) {
actual = evaluationDetails.getVariant();
} else if ("reason".equals(key)) {
actual = evaluationDetails.getReason();
} else if ("error_code".equals(key)) {
actual = evaluationDetails.getErrorCode();
if (actual != null) {
actual = actual.toString();
}
} else {
throw new IllegalArgumentException(key + " is not a valid key");
}
assertEquals(expected, actual);
}
}
}
}

View File

@ -0,0 +1,26 @@
package dev.openfeature.sdk.e2e.steps;
import static dev.openfeature.sdk.testutils.TestFlagsUtils.buildFlags;
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.e2e.State;
import dev.openfeature.sdk.providers.memory.Flag;
import dev.openfeature.sdk.providers.memory.InMemoryProvider;
import io.cucumber.java.en.Given;
import java.util.Map;
public class ProviderSteps {
private final State state;
public ProviderSteps(State state) {
this.state = state;
}
@Given("a stable provider")
public void aStableProvider() {
Map<String, Flag<?>> flags = buildFlags();
InMemoryProvider provider = new InMemoryProvider(flags);
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
state.client = OpenFeatureAPI.getInstance().getClient();
}
}

View File

@ -1,8 +1,7 @@
package dev.openfeature.sdk.e2e.evaluation;
package dev.openfeature.sdk.e2e.steps;
import static dev.openfeature.sdk.testutils.TestFlagsUtils.buildFlags;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.EvaluationContext;
@ -289,7 +288,7 @@ public class StepDefinitions {
@Then("the reason should indicate an error and the error code should indicate a missing flag with {string}")
public void the_reason_should_indicate_an_error_and_the_error_code_should_be_flag_not_found(String errorCode) {
assertEquals(Reason.ERROR.toString(), notFoundDetails.getReason());
assertTrue(notFoundDetails.getErrorCode().name().equals(errorCode));
assertEquals(errorCode, notFoundDetails.getErrorCode().name());
}
// type mismatch
@ -309,6 +308,23 @@ public class StepDefinitions {
@Then("the reason should indicate an error and the error code should indicate a type mismatch with {string}")
public void the_reason_should_indicate_an_error_and_the_error_code_should_be_type_mismatch(String errorCode) {
assertEquals(Reason.ERROR.toString(), typeErrorDetails.getReason());
assertTrue(typeErrorDetails.getErrorCode().name().equals(errorCode));
assertEquals(errorCode, typeErrorDetails.getErrorCode().name());
}
@SuppressWarnings("java:S2925")
@When("sleep for {int} milliseconds")
public void sleepForMilliseconds(int millis) {
long startTime = System.currentTimeMillis();
long endTime = startTime + millis;
long now;
while ((now = System.currentTimeMillis()) < endTime) {
long remainingTime = endTime - now;
try {
//noinspection BusyWait
Thread.sleep(remainingTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@ -3,21 +3,28 @@ package dev.openfeature.sdk.providers.memory;
import static dev.openfeature.sdk.Structure.mapToStructure;
import static dev.openfeature.sdk.testutils.TestFlagsUtils.buildFlags;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableMap;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.EventDetails;
import dev.openfeature.sdk.ImmutableContext;
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.OpenFeatureAPITestUtil;
import dev.openfeature.sdk.Value;
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
import dev.openfeature.sdk.exceptions.TypeMismatchError;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import lombok.SneakyThrows;
import org.junit.jupiter.api.BeforeEach;
@ -25,18 +32,21 @@ import org.junit.jupiter.api.Test;
class InMemoryProviderTest {
private static Client client;
private Client client;
private static InMemoryProvider provider;
private InMemoryProvider provider;
private OpenFeatureAPI api;
@SneakyThrows
@BeforeEach
void beforeEach() {
final var configChangedEventCounter = new AtomicInteger();
Map<String, Flag<?>> flags = buildFlags();
provider = spy(new InMemoryProvider(flags));
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(eventDetails -> {});
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
client = OpenFeatureAPI.getInstance().getClient();
api = OpenFeatureAPITestUtil.createAPI();
api.onProviderConfigurationChanged(eventDetails -> configChangedEventCounter.incrementAndGet());
api.setProviderAndWait(provider);
client = api.getClient();
provider.updateFlags(flags);
provider.updateFlag(
"addedFlag",
@ -45,6 +55,11 @@ class InMemoryProviderTest {
.variant("off", false)
.defaultVariant("on")
.build());
// wait for the two config changed events to be fired, otherwise they could mess with our tests
while (configChangedEventCounter.get() < 2) {
Thread.sleep(1);
}
}
@Test
@ -107,8 +122,8 @@ class InMemoryProviderTest {
Consumer<EventDetails> handler = mock(Consumer.class);
Map<String, Flag<?>> flags = buildFlags();
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler);
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
api.onProviderConfigurationChanged(handler);
api.setProviderAndWait(provider);
provider.updateFlags(flags);

View File

@ -1,31 +0,0 @@
package dev.openfeature.sdk.testutils;
import static org.awaitility.Awaitility.await;
import dev.openfeature.sdk.FeatureProvider;
import dev.openfeature.sdk.OpenFeatureAPI;
import java.time.Duration;
import java.util.function.Function;
import lombok.experimental.UtilityClass;
// todo check the need of this utility class as we now have setProviderAndWait capability
@UtilityClass
public class FeatureProviderTestUtils {
public static void setFeatureProvider(FeatureProvider provider) {
OpenFeatureAPI.getInstance().setProvider(provider);
waitForProviderInitializationComplete(OpenFeatureAPI::getProvider, provider);
}
private static void waitForProviderInitializationComplete(
Function<OpenFeatureAPI, FeatureProvider> extractor, FeatureProvider provider) {
await().pollDelay(Duration.ofMillis(1))
.atMost(Duration.ofSeconds(1))
.until(() -> extractor.apply(OpenFeatureAPI.getInstance()).equals(provider));
}
public static void setFeatureProvider(String domain, FeatureProvider provider) {
OpenFeatureAPI.getInstance().setProvider(domain, provider);
waitForProviderInitializationComplete(api -> api.getProvider(domain), provider);
}
}

View File

@ -3,6 +3,7 @@ package dev.openfeature.sdk.testutils;
import static dev.openfeature.sdk.Structure.mapToStructure;
import com.google.common.collect.ImmutableMap;
import dev.openfeature.sdk.ImmutableMetadata;
import dev.openfeature.sdk.Value;
import dev.openfeature.sdk.providers.memory.Flag;
import java.util.HashMap;
@ -22,9 +23,11 @@ public class TestFlagsUtils {
public static final String OBJECT_FLAG_KEY = "object-flag";
public static final String CONTEXT_AWARE_FLAG_KEY = "context-aware";
public static final String WRONG_FLAG_KEY = "wrong-flag";
public static final String METADATA_FLAG_KEY = "metadata-flag";
/**
* Building flags for testing purposes.
*
* @return map of flags
*/
public static Map<String, Flag<?>> buildFlags() {
@ -90,6 +93,19 @@ public class TestFlagsUtils {
.variant("two", "dos")
.defaultVariant("one")
.build());
flags.put(
METADATA_FLAG_KEY,
Flag.builder()
.variant("on", true)
.variant("off", false)
.defaultVariant("on")
.flagMetadata(ImmutableMetadata.builder()
.addString("string", "1.0.2")
.addInteger("integer", 2)
.addBoolean("boolean", true)
.addDouble("float", 0.1d)
.build())
.build());
return flags;
}
}

View File

@ -1 +1 @@
1.14.1
1.16.0