Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ build
result*

# Ignore IDE-specific directory
.idea
.idea

# Ignore generated files
ffi/swig
ffi/src/main/resources
ffi/src/main/java/com/antithesis/ffi/internal/FfiWrapperJNI.java
67 changes: 43 additions & 24 deletions sdk/src/main/java/com/antithesis/sdk/Assert.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,31 @@
*/
final public class Assert {

private static final ObjectMapper MAPPER = new ObjectMapper();

/**
* Default constructor
*/
public Assert() {
}

/**
* Returns a new details node with {@code guidanceData} merged in, without
* mutating the caller-supplied {@code details}. A {@code null} details
* argument is treated as an empty object.
*/
private static ObjectNode mergeGuidance(final ObjectNode details, final ObjectNode guidanceData) {
if (details == null) {
return guidanceData;
}
// Create a shallow copy of the details
ObjectNode merged = MAPPER.createObjectNode();
merged.setAll(details);
// Add guidance fields on top
merged.setAll(guidanceData);
return merged;
}

/**
* Assert that <code>condition</code> is true every time this function is called, <i>and</i> that it is
* called at least once. The corresponding test property will be viewable in the <code>Antithesis SDK: Always</code> group of your triage report.
Expand Down Expand Up @@ -347,8 +366,8 @@ private static void guidanceHelper(
public static <T extends Number> void alwaysGreaterThan(final T left, final T right, final String message, final ObjectNode details) {
double leftValue = left.doubleValue();
double rightValue = right.doubleValue();
ObjectNode guidanceData = new ObjectMapper().createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode guidanceData = MAPPER.createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
alwaysHelper(leftValue > rightValue, message, detailsExtended);
guidanceHelper(GuidanceType.Numeric, guidanceData, false, message);
}
Expand All @@ -372,8 +391,8 @@ public static <T extends Number> void alwaysGreaterThan(final T left, final T ri
public static <T extends Number> void alwaysGreaterThanOrEqualTo(final T left, final T right, final String message, final ObjectNode details) {
double leftValue = left.doubleValue();
double rightValue = right.doubleValue();
ObjectNode guidanceData = new ObjectMapper().createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode guidanceData = MAPPER.createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
alwaysHelper(leftValue >= rightValue, message, detailsExtended);
guidanceHelper(GuidanceType.Numeric, guidanceData, false, message);
}
Expand All @@ -397,8 +416,8 @@ public static <T extends Number> void alwaysGreaterThanOrEqualTo(final T left, f
public static <T extends Number> void alwaysLessThan(final T left, final T right, final String message, final ObjectNode details) {
double leftValue = left.doubleValue();
double rightValue = right.doubleValue();
ObjectNode guidanceData = new ObjectMapper().createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode guidanceData = MAPPER.createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
alwaysHelper(leftValue < rightValue, message, detailsExtended);
guidanceHelper(GuidanceType.Numeric, guidanceData, true, message);
}
Expand All @@ -422,8 +441,8 @@ public static <T extends Number> void alwaysLessThan(final T left, final T right
public static <T extends Number> void alwaysLessThanOrEqualTo(final T left, final T right, final String message, final ObjectNode details) {
double leftValue = left.doubleValue();
double rightValue = right.doubleValue();
ObjectNode guidanceData = new ObjectMapper().createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode guidanceData = MAPPER.createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
alwaysHelper(leftValue <= rightValue, message, detailsExtended);
guidanceHelper(GuidanceType.Numeric, guidanceData, true, message);
}
Expand All @@ -447,10 +466,10 @@ public static <T extends Number> void alwaysLessThanOrEqualTo(final T left, fina
public static <T extends Number> void sometimesGreaterThan(final T left, final T right, final String message, final ObjectNode details) {
double leftValue = left.doubleValue();
double rightValue = right.doubleValue();
ObjectNode guidanceData = new ObjectMapper().createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode guidanceData = MAPPER.createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
sometimesHelper(leftValue > rightValue, message, detailsExtended);
guidanceHelper(GuidanceType.Numeric, guidanceData, false, message);
guidanceHelper(GuidanceType.Numeric, guidanceData, true, message);
}

/**
Expand All @@ -472,10 +491,10 @@ public static <T extends Number> void sometimesGreaterThan(final T left, final T
public static <T extends Number> void sometimesGreaterThanOrEqualTo(final T left, final T right, final String message, final ObjectNode details) {
double leftValue = left.doubleValue();
double rightValue = right.doubleValue();
ObjectNode guidanceData = new ObjectMapper().createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode guidanceData = MAPPER.createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
sometimesHelper(leftValue >= rightValue, message, detailsExtended);
guidanceHelper(GuidanceType.Numeric, guidanceData, false, message);
guidanceHelper(GuidanceType.Numeric, guidanceData, true, message);
}

/**
Expand All @@ -497,10 +516,10 @@ public static <T extends Number> void sometimesGreaterThanOrEqualTo(final T left
public static <T extends Number> void sometimesLessThan(final T left, final T right, final String message, final ObjectNode details) {
double leftValue = left.doubleValue();
double rightValue = right.doubleValue();
ObjectNode guidanceData = new ObjectMapper().createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode guidanceData = MAPPER.createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
sometimesHelper(leftValue < rightValue, message, detailsExtended);
guidanceHelper(GuidanceType.Numeric, guidanceData, true, message);
guidanceHelper(GuidanceType.Numeric, guidanceData, false, message);
}

/**
Expand All @@ -522,10 +541,10 @@ public static <T extends Number> void sometimesLessThan(final T left, final T ri
public static <T extends Number> void sometimesLessThanOrEqualTo(final T left, final T right, final String message, final ObjectNode details) {
double leftValue = left.doubleValue();
double rightValue = right.doubleValue();
ObjectNode guidanceData = new ObjectMapper().createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode guidanceData = MAPPER.createObjectNode().put("left", leftValue).put("right", rightValue);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
sometimesHelper(leftValue <= rightValue, message, detailsExtended);
guidanceHelper(GuidanceType.Numeric, guidanceData, true, message);
guidanceHelper(GuidanceType.Numeric, guidanceData, false, message);
}

/**
Expand All @@ -548,9 +567,9 @@ public static <T extends Number> void sometimesLessThanOrEqualTo(final T left, f
* @see Assert#always always
*/
public static void alwaysSome(final Map<String, Boolean> conditions, final String message, final ObjectNode details) {
ObjectNode guidanceData = new ObjectMapper().createObjectNode();
ObjectNode guidanceData = MAPPER.createObjectNode();
conditions.forEach(guidanceData::put);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
alwaysHelper(conditions.containsValue(true), message, detailsExtended);
guidanceHelper(GuidanceType.Boolean, guidanceData, false, message);
}
Expand All @@ -575,9 +594,9 @@ public static void alwaysSome(final Map<String, Boolean> conditions, final Strin
* @see Assert#sometimes sometimes
*/
public static void sometimesAll(final Map<String, Boolean> conditions, final String message, final ObjectNode details) {
ObjectNode guidanceData = new ObjectMapper().createObjectNode();
ObjectNode guidanceData = MAPPER.createObjectNode();
conditions.forEach(guidanceData::put);
ObjectNode detailsExtended = (ObjectNode) details.setAll(guidanceData);
ObjectNode detailsExtended = mergeGuidance(details, guidanceData);
sometimesHelper(!conditions.containsValue(false), message, detailsExtended);
guidanceHelper(GuidanceType.Boolean, guidanceData, true, message);
}
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/java/com/antithesis/sdk/internal/Assertion.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public static LocationInfo getLocationInfo(final String id) {
return maybeTrackingInfo.getLocationInfo();
}

// Visible for testing: clears per-assertion tracking so tests start fresh.
static void resetTracking() {
TRACKER.clear();
}

public void trackEntry() {
TrackingInfo trackingInfo = TRACKER.computeIfAbsent(this.id, (key) -> {
return new TrackingInfo(this.location);
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/java/com/antithesis/sdk/internal/Guidance.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public void serialize(GuidanceType value, JsonGenerator jsonGen, SerializerProvi
@JsonProperty("hit")
final private boolean hit;

// Visible for testing: clears per-guidance tracking so tests start fresh.
static void resetTracking() {
NUMERIC_TRACKERS.clear();
}

public void trackEntry() {
if (!this.hit) {
this.emit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public static OutputHandler get() {
return HANDLER_INSTANCE;
}

// Visible for testing: install a specific output handler (e.g. an in-memory
// capture) so tests can observe what the SDK emits.
static void useHandler(final OutputHandler handler) {
HANDLER_INSTANCE = handler;
}

private static synchronized OutputHandler getInternal() {
if (HANDLER_INSTANCE == null) {
HANDLER_INSTANCE =
Expand Down
114 changes: 114 additions & 0 deletions sdk/src/test/java/com/antithesis/sdk/AssertBooleanGuidanceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.antithesis.sdk;

import com.antithesis.sdk.internal.CaptureSupport;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Covers {@link Assert#alwaysSome} and {@link Assert#sometimesAll}: their
* boolean condition semantics (OR / AND), the boolean guidance direction, and
* the propositions carried in guidance_data.
*/
public class AssertBooleanGuidanceTest {

private final ObjectMapper mapper = new ObjectMapper();
private CaptureSupport capture;

@BeforeEach
void setUp() {
capture = CaptureSupport.install();
}

@AfterEach
void cleanUp() {
CaptureSupport.uninstall();
}

private ObjectNode details() {
return mapper.createObjectNode();
}

private Map<String, Boolean> map(final Boolean a, final Boolean b) {
Map<String, Boolean> m = new LinkedHashMap<>();
m.put("a", a);
m.put("b", b);
return m;
}

private JsonNode assertion(final String id) {
return capture.assertionsFor(id).get(0);
}

private JsonNode guidance(final String id) {
return capture.guidanceFor(id).get(0);
}

@Test
void alwaysSomeIsTrueWhenAnyConditionTrue() {
Assert.alwaysSome(map(false, true), "some-any-true", details());
assertEquals(true, assertion("some-any-true").get("condition").asBoolean());
}

@Test
void alwaysSomeIsFalseWhenAllConditionsFalse() {
Assert.alwaysSome(map(false, false), "some-all-false", details());
assertEquals(false, assertion("some-all-false").get("condition").asBoolean());
}

@Test
void alwaysSomeGuidanceShape() {
Assert.alwaysSome(map(true, false), "some-shape", details());
JsonNode g = guidance("some-shape");
assertEquals("boolean", g.get("guidance_type").asText(), "guidance_type");
assertEquals(false, g.get("maximize").asBoolean(), "alwaysSome uses maximize=false");
assertEquals(true, g.get("guidance_data").get("a").asBoolean(), "proposition a");
assertEquals(false, g.get("guidance_data").get("b").asBoolean(), "proposition b");
}

@Test
void sometimesAllIsTrueWhenNoConditionFalse() {
Assert.sometimesAll(map(true, true), "all-none-false", details());
assertEquals(true, assertion("all-none-false").get("condition").asBoolean());
}

@Test
void sometimesAllIsFalseWhenAnyConditionFalse() {
Assert.sometimesAll(map(true, false), "all-any-false", details());
assertEquals(false, assertion("all-any-false").get("condition").asBoolean());
}

@Test
void sometimesAllGuidanceShape() {
Assert.sometimesAll(map(true, true), "all-shape", details());
JsonNode g = guidance("all-shape");
assertEquals("boolean", g.get("guidance_type").asText(), "guidance_type");
assertEquals(true, g.get("maximize").asBoolean(), "sometimesAll uses maximize=true");
}

// --- empty-map corner cases (identity of OR is false, identity of AND is true) ---

@Test
void alwaysSomeWithEmptyMapIsFalse() {
Assert.alwaysSome(new LinkedHashMap<>(), "some-empty", details());
assertEquals(false, assertion("some-empty").get("condition").asBoolean(),
"OR of no conditions is false");
}

@Test
void sometimesAllWithEmptyMapIsTrue() {
Assert.sometimesAll(new LinkedHashMap<>(), "all-empty", details());
assertEquals(true, assertion("all-empty").get("condition").asBoolean(),
"AND of no conditions is true");
}
}
Loading
Loading