diff --git a/src/main/java/com/yahoo/bullet/BulletConfig.java b/src/main/java/com/yahoo/bullet/BulletConfig.java index 512884f1..28497874 100644 --- a/src/main/java/com/yahoo/bullet/BulletConfig.java +++ b/src/main/java/com/yahoo/bullet/BulletConfig.java @@ -7,8 +7,6 @@ import lombok.extern.slf4j.Slf4j; -import java.io.IOException; - @Slf4j public class BulletConfig extends Config { public static final String SPECIFICATION_DEFAULT_DURATION = "bullet.query.default.duration"; @@ -55,9 +53,15 @@ public class BulletConfig extends Config { * Constructor that loads specific file augmented with defaults. * * @param file YAML file to load. - * @throws IOException if an error occurred with the file loading. */ - public BulletConfig(String file) throws IOException { + public BulletConfig(String file) { super(file, DEFAULT_CONFIGURATION_NAME); } + + /** + * Constructor that loads just the defaults. + */ + public BulletConfig() { + super(DEFAULT_CONFIGURATION_NAME); + } } diff --git a/src/main/java/com/yahoo/bullet/Config.java b/src/main/java/com/yahoo/bullet/Config.java index 76207769..c425732a 100644 --- a/src/main/java/com/yahoo/bullet/Config.java +++ b/src/main/java/com/yahoo/bullet/Config.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -29,11 +30,10 @@ public class Config implements Serializable { * Constructor that loads a specific file and loads the settings in that file. * * @param file YAML file to load. - * @throws IOException if an error occurred with the file loading. */ - public Config(String file) throws IOException { + public Config(String file) { data = readYAML(file); - log.info("Configuration: {} ", data); + log.info("Final Configuration:\n{} ", data); } /** @@ -41,14 +41,13 @@ public Config(String file) throws IOException { * * @param file YAML file to load. * @param defaultConfigurationFile Default YAML file to load. - * @throws IOException if an error occurred with the file loading. */ - public Config(String file, String defaultConfigurationFile) throws IOException { - this(defaultConfigurationFile); + public Config(String file, String defaultConfigurationFile) { + data = readYAML(defaultConfigurationFile); // Override Map specificConf = readYAML(file); data.putAll(specificConf); - log.info("Final configuration: {} ", data); + log.info("Final Configuration with defaults:\n{} ", data); } /** @@ -73,6 +72,47 @@ public Object getOrDefault(String key, Object defaultValue) { return value != null ? value : defaultValue; } + /** + * Get a value from the config as a particular type. + * + * @param key The name of the config. + * @param clazz The Class of the type. + * @param The type of the config. + * @return The config as the particular type or null. + * @throws ClassCastException if the value of the config could not be casted to the type. + */ + public T getAs(String key, Class clazz) { + return clazz.cast(get(key)); + } + + /** + * Get a value from the config as a particular type or default to a provided value. + * + * @param key The name of the config. + * @param defaultValue A default of the same type to use if the config was not found. + * @param clazz The Class of the type. + * @param The type of the config. + * @return The config or your default value as the particular type. + * @throws ClassCastException if the value of the config or default could not be casted to the type. + */ + public T getOrDefaultAs(String key, T defaultValue, Class clazz) { + return clazz.cast(getOrDefault(key, defaultValue)); + } + + /** + * Get a value from the config as a particular type or throw an exception with a message if not found. + * + * @param key The name of the config. + * @param clazz The Class of the type. + * @param The type of the config. + * @return The config as the particular type. + * @throws ClassCastException if the value of the config could not be casted to the type. + * @throws NullPointerException if the config was not found. + */ + public T getRequiredConfigAs(String key, Class clazz) { + return Objects.requireNonNull(getAs(key, clazz), "Required value for " + key + " was missing"); + } + /** * Gets all mappings for a set of keys. If no keys are specified, all mappings * are returned. @@ -152,15 +192,19 @@ public void clear() { * * @param yamlFile The String name of the YAML resource file in classpath or the path to a YAML file containing the mappings. * @return A {@link Map} of String names to Objects of the mappings in the YAML file. - * @throws IOException if the resource could not be read. */ - protected Map readYAML(String yamlFile) throws IOException { - if (yamlFile != null && yamlFile.length() > 0) { - log.info("Loading configuration file: {}", yamlFile); + protected Map readYAML(String yamlFile) { + if (yamlFile == null || yamlFile.isEmpty()) { + return new HashMap<>(); + } + log.info("Loading configuration file: {}", yamlFile); + try { InputStream is = this.getClass().getResourceAsStream("/" + yamlFile); Reader reader = (is != null ? new InputStreamReader(is) : new FileReader(yamlFile)); return (Map) YAML.load(reader); + } catch (IOException ioe) { + log.error("Error loading configuration", ioe); + return new HashMap<>(); } - return new HashMap<>(); } } diff --git a/src/main/java/com/yahoo/bullet/pubsub/Metadata.java b/src/main/java/com/yahoo/bullet/pubsub/Metadata.java index e430f482..c70cf0b0 100644 --- a/src/main/java/com/yahoo/bullet/pubsub/Metadata.java +++ b/src/main/java/com/yahoo/bullet/pubsub/Metadata.java @@ -5,21 +5,59 @@ */ package com.yahoo.bullet.pubsub; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.io.Serializable; -@Getter @Setter @AllArgsConstructor @NoArgsConstructor +@NoArgsConstructor public class Metadata implements Serializable { public enum Signal { ACKNOWLEDGE, - COMPLETE + COMPLETE, + FAIL } + + private static final long serialVersionUID = 4234800234857923112L; + + @Getter @Setter private Signal signal; - private Serializable content; + + // This is a Serializable object enforced through the constructor, getter and setter. Storing it as an Object so + // GSON can reify an instance. + private Object content; + + /** + * Allows you to create an instance with a {@link com.yahoo.bullet.pubsub.Metadata.Signal} and a + * {@link Serializable} object. + * + * @param signal The signal to set. + * @param object The object that is the metadata. + */ + public Metadata(Signal signal, Serializable object) { + this.signal = signal; + this.content = object; + } + + /** + * Set a serializable content for this metadata. + * + * @param content The content for this metadata. + */ + public void setContent(Serializable content) { + this.content = content; + } + + /** + * Returns the {@link Serializable} content in this metadata. + * + * @return The serializable content or null. + */ + public Serializable getContent() { + return (Serializable) content; + + } /** * Check if Metadata has content. @@ -38,4 +76,14 @@ public boolean hasContent() { public boolean hasSignal() { return signal != null; } + + /** + * Check if Metadata has the given signal. + * + * @param signal The signal to check against. + * @return true if message has {@link Metadata#signal} + */ + public boolean hasSignal(Signal signal) { + return hasSignal() && this.signal == signal; + } } diff --git a/src/main/java/com/yahoo/bullet/pubsub/PubSub.java b/src/main/java/com/yahoo/bullet/pubsub/PubSub.java index 6e12a528..01d9c0d5 100644 --- a/src/main/java/com/yahoo/bullet/pubsub/PubSub.java +++ b/src/main/java/com/yahoo/bullet/pubsub/PubSub.java @@ -9,7 +9,6 @@ import java.lang.reflect.Constructor; import java.util.List; -import java.util.Objects; /** * Notation: Partition is a unit of parallelism in the Pub/Sub queue. @@ -113,7 +112,7 @@ public static PubSub from(BulletConfig config) throws PubSubException { */ public T getRequiredConfig(Class clazz, String name) throws PubSubException { try { - return clazz.cast(Objects.requireNonNull(config.get(name))); + return config.getRequiredConfigAs(name, clazz); } catch (Exception e) { throw PubSubException.forArgument(name, e); } diff --git a/src/main/java/com/yahoo/bullet/pubsub/PubSubMessage.java b/src/main/java/com/yahoo/bullet/pubsub/PubSubMessage.java index 463f0b27..600742b7 100644 --- a/src/main/java/com/yahoo/bullet/pubsub/PubSubMessage.java +++ b/src/main/java/com/yahoo/bullet/pubsub/PubSubMessage.java @@ -6,6 +6,7 @@ package com.yahoo.bullet.pubsub; import com.yahoo.bullet.pubsub.Metadata.Signal; +import com.yahoo.bullet.result.JSONFormatter; import lombok.Getter; import java.io.Serializable; @@ -17,7 +18,7 @@ * emitted by Bullet. */ @Getter -public class PubSubMessage implements Serializable { +public class PubSubMessage implements Serializable, JSONFormatter { private static final long serialVersionUID = 2407848310969237888L; private String id; @@ -25,6 +26,13 @@ public class PubSubMessage implements Serializable { private String content; private Metadata metadata; + /** + * Constructor for a message having no information. Used internally. Not recommended for use. + */ + public PubSubMessage() { + this("", null); + } + /** * Constructor for a message having only content. * @@ -62,14 +70,14 @@ public PubSubMessage(String id, String content, Metadata metadata) { * * @param id The ID associated with the message. * @param content The content of the message. - * @param signal The Metadata.Signal to be sent with the message. + * @param signal The Signal to be sent with the message. */ public PubSubMessage(String id, String content, Signal signal) { this(id, content, signal, -1); } /** - * Constructor for a message having content, a {@link Metadata.Signal} and a sequence number. + * Constructor for a message having content, a {@link Signal} and a sequence number. * * @param id The ID associated with the message. * @param content The content of the message. @@ -113,6 +121,25 @@ public boolean hasMetadata() { return metadata != null; } + /** + * Check if message has a given {@link Signal}. + * + * @param signal The signal to check for. + * @return true if message has the given signal. + */ + public boolean hasSignal(Signal signal) { + return hasMetadata() && metadata.hasSignal(signal); + } + + /** + * Check if the message has a {@link Signal}. + * + * @return true if message has a signal. + */ + public boolean hasSignal() { + return hasMetadata() && metadata.hasSignal(); + } + @Override public int hashCode() { return (id + sequence).hashCode(); @@ -126,4 +153,24 @@ public boolean equals(Object other) { PubSubMessage otherMessage = (PubSubMessage) other; return id.equals(otherMessage.getId()) && sequence == otherMessage.getSequence(); } + + @Override + public String toString() { + return asJSON(); + } + + @Override + public String asJSON() { + return JSONFormatter.asJSON(this); + } + + /** + * Converts a json representation back to an instance. + * + * @param json The string representation of the JSON. + * @return An instance of this class. + */ + public static PubSubMessage fromJSON(String json) { + return JSONFormatter.fromJSON(json, PubSubMessage.class); + } } diff --git a/src/main/java/com/yahoo/bullet/result/JSONFormatter.java b/src/main/java/com/yahoo/bullet/result/JSONFormatter.java index 644bc2d1..5dd75d02 100644 --- a/src/main/java/com/yahoo/bullet/result/JSONFormatter.java +++ b/src/main/java/com/yahoo/bullet/result/JSONFormatter.java @@ -24,6 +24,18 @@ static String asJSON(Object object) { return GSON.toJson(object); } + /** + * Returns a deserialized object from JSON using {@link JSONFormatter#GSON}. + * + * @param json The String json that represents the object. + * @param clazz The class of the object. + * @param The type of the object. It must implement {@link JSONFormatter}. + * @return An instance of the object deserialized from JSON. + */ + static T fromJSON(String json, Class clazz) { + return GSON.fromJson(json, clazz); + } + /** * Convert this object to a JSON string. * @return The JSON representation of this. diff --git a/src/test/java/com/yahoo/bullet/BulletConfigTest.java b/src/test/java/com/yahoo/bullet/BulletConfigTest.java index e582583c..40a56ca4 100644 --- a/src/test/java/com/yahoo/bullet/BulletConfigTest.java +++ b/src/test/java/com/yahoo/bullet/BulletConfigTest.java @@ -8,25 +8,34 @@ import org.testng.Assert; import org.testng.annotations.Test; -import java.io.IOException; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; public class BulletConfigTest { - @Test - public void testNoFiles() throws IOException { - BulletConfig config = new BulletConfig(null); + public void testNoFiles() { + BulletConfig config = new BulletConfig(); + Assert.assertEquals(config.get(BulletConfig.SPECIFICATION_MAX_DURATION), 120000L); + + config = new BulletConfig(null); Assert.assertEquals(config.get(BulletConfig.SPECIFICATION_MAX_DURATION), 120000L); + config = new BulletConfig(""); Assert.assertEquals(config.get(BulletConfig.SPECIFICATION_MAX_DURATION), 120000L); } @Test - public void testCustomConfig() throws IOException { + public void testMissingFile() { + BulletConfig config = new BulletConfig("/path/to/non/existant/file"); + Assert.assertEquals(config.get(BulletConfig.SPECIFICATION_MAX_DURATION), 120000L); + } + + @Test + public void testCustomConfig() { BulletConfig config = new BulletConfig("src/test/resources/test_config.yaml"); Assert.assertEquals(config.get(BulletConfig.SPECIFICATION_MAX_DURATION), 10000L); Assert.assertEquals(config.get(BulletConfig.AGGREGATION_MAX_SIZE), 100L); @@ -34,7 +43,7 @@ public void testCustomConfig() throws IOException { } @Test - public void testCustomProperties() throws IOException { + public void testCustomProperties() { BulletConfig config = new BulletConfig(null); Assert.assertNull(config.get("foo")); config.set("foo", "bar"); @@ -42,7 +51,7 @@ public void testCustomProperties() throws IOException { } @Test - public void testGettingWithDefault() throws IOException { + public void testGettingWithDefault() { BulletConfig config = new BulletConfig("src/test/resources/test_config.yaml"); Assert.assertEquals(config.getOrDefault(BulletConfig.AGGREGATION_COMPOSITE_FIELD_SEPARATOR, ";"), "|"); Assert.assertEquals(config.getOrDefault("does.not.exist", "foo"), "foo"); @@ -50,7 +59,7 @@ public void testGettingWithDefault() throws IOException { } @Test - public void testGettingMultipleProperties() throws IOException { + public void testGettingMultipleProperties() { BulletConfig config = new BulletConfig(null); config.clear(); config.set("1", 1); @@ -73,7 +82,7 @@ public void testGettingMultipleProperties() throws IOException { } @Test - public void testGettingMaskedProperties() throws IOException { + public void testGettingMaskedProperties() { BulletConfig config = new BulletConfig(null); config.clear(); config.set("1", 1); @@ -96,7 +105,7 @@ public void testGettingMaskedProperties() throws IOException { } @Test - public void testMerging() throws IOException { + public void testMerging() { BulletConfig config = new BulletConfig("src/test/resources/test_config.yaml"); int configSize = config.getAll(Optional.empty()).size(); @@ -120,7 +129,7 @@ public void testMerging() throws IOException { } @Test - public void testPropertiesWithPrefix() throws IOException { + public void testPropertiesWithPrefix() { BulletConfig config = new BulletConfig("src/test/resources/test_config.yaml"); String prefix = "bullet.pubsub"; String fieldValue = "com.yahoo.bullet.pubsub.MockPubSub"; @@ -133,7 +142,7 @@ public void testPropertiesWithPrefix() throws IOException { } @Test - public void testPropertiesStripPrefix() throws IOException { + public void testPropertiesStripPrefix() { BulletConfig config = new BulletConfig("src/test/resources/test_config.yaml"); String prefix = "bullet.pubsub."; String fieldName = "class.name"; @@ -146,4 +155,55 @@ public void testPropertiesStripPrefix() throws IOException { Assert.assertNull(properties.get(BulletConfig.PUBSUB_CLASS_NAME)); Assert.assertEquals(properties.get(fieldName), fieldValue); } + + @Test + public void testGetAsAGivenType() { + BulletConfig config = new BulletConfig("src/test/resources/custom_config.yaml"); + + long defaulted = config.getAs(BulletConfig.DISTRIBUTION_AGGREGATION_MAX_POINTS, Long.class); + Assert.assertEquals(defaulted, 100); + + Map customMap = config.getAs("my.custom.map", Map.class); + Assert.assertNotNull(customMap); + Assert.assertEquals(customMap.size(), 2); + Assert.assertEquals(customMap.get("first"), 10L); + Assert.assertEquals(customMap.get("second"), 42L); + + List customList = config.getAs("my.custom.list", List.class); + Assert.assertNotNull(customList); + Assert.assertEquals(customList.size(), 2); + Assert.assertEquals(customList.get(0), "foo"); + Assert.assertEquals(customList.get(1), "bar"); + } + + @Test + public void testGetOrDefaultAsAGivenType() { + BulletConfig config = new BulletConfig("src/test/resources/test_config.yaml"); + + + long notDefaulted = config.getOrDefaultAs(BulletConfig.DISTRIBUTION_AGGREGATION_MAX_POINTS, 42L, Long.class); + Assert.assertEquals(notDefaulted, 100); + + String defaulted = config.getOrDefaultAs("foo", "value", String.class); + Assert.assertEquals(defaulted, "value"); + + List anotherDefaulted = config.getOrDefaultAs("foo", Arrays.asList("foo", "bar"), List.class); + Assert.assertEquals(anotherDefaulted, Arrays.asList("foo", "bar")); + } + + @Test + public void testGettingRequiredConfig() { + BulletConfig config = new BulletConfig("src/test/resources/test_config.yaml"); + + + long present = config.getRequiredConfigAs(BulletConfig.DISTRIBUTION_AGGREGATION_MAX_POINTS, Long.class); + Assert.assertEquals(present, 100); + } + + @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = ".*was missing.*") + public void testMissingRequiredConfig() { + BulletConfig config = new BulletConfig("src/test/resources/test_config.yaml"); + + config.getRequiredConfigAs("does.not.exist", Long.class); + } } diff --git a/src/test/java/com/yahoo/bullet/pubsub/MetadataTest.java b/src/test/java/com/yahoo/bullet/pubsub/MetadataTest.java index 5b50789a..daf16bfd 100644 --- a/src/test/java/com/yahoo/bullet/pubsub/MetadataTest.java +++ b/src/test/java/com/yahoo/bullet/pubsub/MetadataTest.java @@ -5,21 +5,34 @@ */ package com.yahoo.bullet.pubsub; +import com.yahoo.bullet.pubsub.Metadata.Signal; import org.testng.Assert; import org.testng.annotations.Test; public class MetadataTest { @Test public void testHasSignal() { - Metadata full = new Metadata(Metadata.Signal.ACKNOWLEDGE, 5); + Metadata full = new Metadata(Signal.ACKNOWLEDGE, 5); Metadata empty = new Metadata(); Assert.assertTrue(full.hasSignal()); Assert.assertFalse(empty.hasSignal()); } + @Test + public void testSignalTypes() { + Metadata empty = new Metadata(); + Assert.assertFalse(empty.hasSignal()); + + Assert.assertTrue(new Metadata(Signal.ACKNOWLEDGE, null).hasSignal(Signal.ACKNOWLEDGE)); + Assert.assertTrue(new Metadata(Signal.FAIL, null).hasSignal(Signal.FAIL)); + Assert.assertTrue(new Metadata(Signal.COMPLETE, null).hasSignal(Signal.COMPLETE)); + + Assert.assertFalse(new Metadata(Signal.ACKNOWLEDGE, null).hasSignal(Signal.FAIL)); + } + @Test public void testHasContent() { - Metadata full = new Metadata(Metadata.Signal.ACKNOWLEDGE, 5); + Metadata full = new Metadata(Signal.ACKNOWLEDGE, 5); Metadata empty = new Metadata(); Assert.assertTrue(full.hasContent()); Assert.assertFalse(empty.hasContent()); @@ -35,7 +48,7 @@ public void testSetContentWhenEmpty() { @Test public void testSetSignalWhenEmpty() { Metadata empty = new Metadata(); - empty.setSignal(Metadata.Signal.ACKNOWLEDGE); - Assert.assertEquals(empty.getSignal(), Metadata.Signal.ACKNOWLEDGE); + empty.setSignal(Signal.ACKNOWLEDGE); + Assert.assertEquals(empty.getSignal(), Signal.ACKNOWLEDGE); } } diff --git a/src/test/java/com/yahoo/bullet/pubsub/PubSubMessageTest.java b/src/test/java/com/yahoo/bullet/pubsub/PubSubMessageTest.java index 517d5723..afb2be7a 100644 --- a/src/test/java/com/yahoo/bullet/pubsub/PubSubMessageTest.java +++ b/src/test/java/com/yahoo/bullet/pubsub/PubSubMessageTest.java @@ -9,8 +9,12 @@ import org.testng.Assert; import org.testng.annotations.Test; +import java.util.ArrayList; +import java.util.Collections; import java.util.UUID; +import static com.yahoo.bullet.TestHelpers.assertJSONEquals; + public class PubSubMessageTest { private String getRandomString() { return UUID.randomUUID().toString(); @@ -141,11 +145,84 @@ public void testHasMetadata() { PubSubMessage message; message = new PubSubMessage(messageID, messageContent); Assert.assertFalse(message.hasMetadata()); + Assert.assertFalse(message.hasSignal()); message = new PubSubMessage(messageID, null, Signal.COMPLETE); Assert.assertTrue(message.hasMetadata()); + Assert.assertTrue(message.hasSignal(Signal.COMPLETE)); message = new PubSubMessage(messageID, null, new Metadata()); Assert.assertTrue(message.hasMetadata()); + Assert.assertFalse(message.hasSignal()); + } + + @Test + public void testToString() { + assertJSONEquals(new PubSubMessage("foo", "bar", new Metadata(Signal.FAIL, 42.0), 42).toString(), + "{ 'id': 'foo', 'sequence': 42, 'content': 'bar', 'metadata': { 'signal': 'FAIL', 'content': 42.0 } }"); + assertJSONEquals(new PubSubMessage("foo", "bar", new Metadata(Signal.COMPLETE, new ArrayList<>())).toString(), + "{ 'id': 'foo', 'sequence': -1, 'content': 'bar', 'metadata': { 'signal': 'COMPLETE', 'content': [] } }"); + assertJSONEquals(new PubSubMessage("foo", "bar", Signal.COMPLETE, 22).toString(), + "{ 'id': 'foo', 'sequence': 22, 'content': 'bar', 'metadata': { 'signal': 'COMPLETE', 'content': null } }"); + assertJSONEquals(new PubSubMessage("foo", "bar", Signal.ACKNOWLEDGE).toString(), + "{ 'id': 'foo', 'sequence': -1, 'content': 'bar', 'metadata': { 'signal': 'ACKNOWLEDGE', 'content': null } }"); + assertJSONEquals(new PubSubMessage("foo", "bar", 34).toString(), + "{ 'id': 'foo', 'sequence': 34, 'content': 'bar', 'metadata': null }"); + assertJSONEquals(new PubSubMessage("foo", "bar").toString(), + "{ 'id': 'foo', 'sequence': -1, 'content': 'bar', 'metadata': null }"); + assertJSONEquals(new PubSubMessage().toString(), + "{ 'id': '', 'sequence': -1, 'content': null, 'metadata': null }"); + } + + @Test + public void testJSONConversion() { + assertJSONEquals(new PubSubMessage("foo", "bar", new Metadata(Signal.FAIL, 42.0), 42).asJSON(), + "{ 'id': 'foo', 'sequence': 42, 'content': 'bar', 'metadata': { 'signal': 'FAIL', 'content': 42.0 } }"); + assertJSONEquals(new PubSubMessage("foo", "bar", new Metadata(Signal.COMPLETE, new ArrayList<>())).asJSON(), + "{ 'id': 'foo', 'sequence': -1, 'content': 'bar', 'metadata': { 'signal': 'COMPLETE', 'content': [] } }"); + assertJSONEquals(new PubSubMessage("foo", "bar", Signal.COMPLETE, 22).asJSON(), + "{ 'id': 'foo', 'sequence': 22, 'content': 'bar', 'metadata': { 'signal': 'COMPLETE', 'content': null } }"); + assertJSONEquals(new PubSubMessage("foo", "bar", Signal.ACKNOWLEDGE).asJSON(), + "{ 'id': 'foo', 'sequence': -1, 'content': 'bar', 'metadata': { 'signal': 'ACKNOWLEDGE', 'content': null } }"); + assertJSONEquals(new PubSubMessage("foo", "bar", 34).asJSON(), + "{ 'id': 'foo', 'sequence': 34, 'content': 'bar', 'metadata': null }"); + assertJSONEquals(new PubSubMessage("foo", "bar").asJSON(), + "{ 'id': 'foo', 'sequence': -1, 'content': 'bar', 'metadata': null }"); + assertJSONEquals(new PubSubMessage().asJSON(), + "{ 'id': '', 'sequence': -1, 'content': null, 'metadata': null }"); + } + + @Test + public void testRecreatingFromJSON() { + PubSubMessage actual = PubSubMessage.fromJSON("{ 'id': 'foo', 'sequence': 42, 'content': 'bar', " + + "'metadata': { 'signal': 'FAIL', 'content': { 'type': null } } }"); + PubSubMessage expected = new PubSubMessage("foo", null, 42); + Assert.assertEquals(actual, expected); + + Assert.assertTrue(actual.hasMetadata()); + Assert.assertTrue(actual.hasSignal()); + Assert.assertTrue(actual.hasSignal(Signal.FAIL)); + Assert.assertEquals(actual.getMetadata().getSignal(), Signal.FAIL); + Assert.assertEquals(actual.getMetadata().getContent(), Collections.singletonMap("type", null)); + } + + @Test + public void testRecreatingBadMessages() { + PubSubMessage actual = PubSubMessage.fromJSON("{ }"); + + Assert.assertEquals(actual.getId(), ""); + Assert.assertEquals(actual.getSequence(), -1); + Assert.assertNull(actual.getMetadata()); + Assert.assertNull(actual.getContent()); + + actual = PubSubMessage.fromJSON("{ 'metadata': { 'signal': 'ACKNOWLEDGE' } }"); + + Assert.assertEquals(actual.getId(), ""); + Assert.assertEquals(actual.getSequence(), -1); + Assert.assertTrue(actual.hasSignal()); + Assert.assertTrue(actual.hasSignal(Signal.ACKNOWLEDGE)); + Assert.assertTrue(actual.getMetadata().hasSignal(Signal.ACKNOWLEDGE)); + Assert.assertNull(actual.getMetadata().getContent()); + Assert.assertNull(actual.getContent()); } } diff --git a/src/test/java/com/yahoo/bullet/querying/FilterQueryTest.java b/src/test/java/com/yahoo/bullet/querying/FilterQueryTest.java index cf847ddb..8384e5f8 100644 --- a/src/test/java/com/yahoo/bullet/querying/FilterQueryTest.java +++ b/src/test/java/com/yahoo/bullet/querying/FilterQueryTest.java @@ -8,6 +8,7 @@ import com.yahoo.bullet.operations.AggregationOperations.AggregationType; import com.yahoo.bullet.operations.FilterOperations.FilterType; import com.yahoo.bullet.parsing.Aggregation; +import com.yahoo.bullet.parsing.ParsingException; import com.yahoo.bullet.result.RecordBox; import org.apache.commons.lang3.tuple.Pair; import org.testng.Assert; @@ -102,4 +103,9 @@ public void testMaximumEmittedWithNonMatchingRecords() { Assert.assertNull(query.getData()); } } + + @Test(expectedExceptions = ParsingException.class) + public void testValidationFail() throws ParsingException { + new FilterQuery("{ 'aggregation': { 'type': null } }", emptyMap()); + } } diff --git a/src/test/java/com/yahoo/bullet/result/ClipTest.java b/src/test/java/com/yahoo/bullet/result/ClipTest.java index c24d6829..c81f4425 100644 --- a/src/test/java/com/yahoo/bullet/result/ClipTest.java +++ b/src/test/java/com/yahoo/bullet/result/ClipTest.java @@ -86,4 +86,12 @@ public void testInvalidDoubles() { makeJSON("{'foo': 'Infinity', 'baz': '-Infinity', 'bar': 'NaN'}", "[{'field': null, 'plus_inf': 'Infinity', 'neg_inf': '-Infinity', 'not_a_number': 'NaN'}]")); } + + @Test + public void testMetadataAddition() { + Clip clip = new Clip(); + clip.add((Metadata) null); + clip.add(new Metadata().add("foo", 1.2)); + assertJSONEquals(clip.asJSON(), makeJSON("{'foo': 1.2}", "[]")); + } } diff --git a/src/test/java/com/yahoo/bullet/result/MetadataTest.java b/src/test/java/com/yahoo/bullet/result/MetadataTest.java index 79c231bb..ba737c61 100644 --- a/src/test/java/com/yahoo/bullet/result/MetadataTest.java +++ b/src/test/java/com/yahoo/bullet/result/MetadataTest.java @@ -79,6 +79,26 @@ public void testErrorsAddition() { Assert.assertEquals(actualErrors.get(3).getResolutions(), singletonList("7")); } + @Test + public void testMetadataWithErrors() { + Error errorA = Error.makeError("foo", "bar"); + Error errorB = Error.makeError("baz", "qux"); + Metadata meta = Metadata.of(Arrays.asList(errorA, errorB)); + Error errorC = Error.makeError("norf", "foo"); + meta.addErrors(Collections.singletonList(errorC)); + + Map actual = meta.asMap(); + Assert.assertEquals(actual.size(), 1); + List actualErrors = (List) actual.get(Metadata.ERROR_KEY); + Assert.assertEquals(actualErrors.size(), 3); + Assert.assertEquals(actualErrors.get(0).getError(), "foo"); + Assert.assertEquals(actualErrors.get(0).getResolutions(), singletonList("bar")); + Assert.assertEquals(actualErrors.get(1).getError(), "baz"); + Assert.assertEquals(actualErrors.get(1).getResolutions(), singletonList("qux")); + Assert.assertEquals(actualErrors.get(2).getError(), "norf"); + Assert.assertEquals(actualErrors.get(2).getResolutions(), singletonList("foo")); + } + @Test public void testMerging() { Metadata metaA = new Metadata(); @@ -117,6 +137,7 @@ public void testConceptKeyExtraction() { configuration.put(BulletConfig.RESULT_METADATA_METRICS, asMetadataEntries(Pair.of("Estimated Result", "foo"), Pair.of("Sketch Metadata", "bar"), + Pair.of("Non Existent", "bar"), Pair.of("Standard Deviations", "baz"))); Set concepts = new HashSet<>(asList(Concept.ESTIMATED_RESULT, diff --git a/src/test/resources/custom_config.yaml b/src/test/resources/custom_config.yaml new file mode 100644 index 00000000..45f24c2b --- /dev/null +++ b/src/test/resources/custom_config.yaml @@ -0,0 +1,6 @@ +my.custom.list: + - foo + - bar +my.custom.map: + first: 10 + second: 42