Skip to content

test(parser): Default parser #315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 8, 2019
Merged
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
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ jdk:
install: true
addons:
srcclr: true
env:
- optimizely_default_parser=GSON_CONFIG_PARSER
- optimizely_default_parser=JACKSON_CONFIG_PARSER
- optimizely_default_parser=JSON_CONFIG_PARSER
- optimizely_default_parser=JSON_SIMPLE_CONFIG_PARSER
script:
- "./gradlew clean"
- "./gradlew exhaustiveTest"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
*/
package com.optimizely.ab.config.parser;

import com.optimizely.ab.internal.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.util.function.Supplier;

/**
* Factory for generating {@link ConfigParser} instances, based on the json parser available on the classpath.
Expand All @@ -37,6 +39,33 @@ public static ConfigParser getInstance() {

//======== Helper methods ========//

public enum ConfigParserSupplier {
GSON_CONFIG_PARSER("com.google.gson.Gson", GsonConfigParser::new),
JACKSON_CONFIG_PARSER("com.fasterxml.jackson.databind.ObjectMapper", JacksonConfigParser::new),
JSON_CONFIG_PARSER("org.json.JSONObject", JsonConfigParser::new),
JSON_SIMPLE_CONFIG_PARSER("org.json.simple.JSONObject", JsonSimpleConfigParser::new);

private final String className;
private final Supplier<ConfigParser> supplier;

ConfigParserSupplier(String className, Supplier<ConfigParser> supplier) {
this.className = className;
this.supplier = supplier;
}

ConfigParser get() {
return supplier.get();
}

private boolean isPresent() {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
/**
* Creates and returns a {@link ConfigParser} using a json parser available on the classpath.
*
Expand All @@ -45,35 +74,39 @@ public static ConfigParser getInstance() {
*/
private static @Nonnull
ConfigParser create() {
ConfigParser configParser;

if (isPresent("com.fasterxml.jackson.databind.ObjectMapper")) {
configParser = new JacksonConfigParser();
} else if (isPresent("com.google.gson.Gson")) {
configParser = new GsonConfigParser();
} else if (isPresent("org.json.simple.JSONObject")) {
configParser = new JsonSimpleConfigParser();
} else if (isPresent("org.json.JSONObject")) {
configParser = new JsonConfigParser();
} else {
throw new MissingJsonParserException("unable to locate a JSON parser. "
+ "Please see <link> for more information");

String configParserName = PropertyUtils.get("default_parser");

if (configParserName != null) {
try {
ConfigParserSupplier supplier = ConfigParserSupplier.valueOf(configParserName);
if (supplier.isPresent()) {
ConfigParser configParser = supplier.get();
logger.debug("using json parser: {}, based on override config", configParser.getClass().getSimpleName());
return configParser;
}

logger.warn("configured parser {} is not available in the classpath", configParserName);
} catch (IllegalArgumentException e) {
logger.warn("configured parser {} is not a valid value", configParserName);
}
}

logger.info("using json parser: {}", configParser.getClass().getSimpleName());
return configParser;
}
for (ConfigParserSupplier supplier: ConfigParserSupplier.values()) {
if (!supplier.isPresent()) {
continue;
}

private static boolean isPresent(@Nonnull String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
ConfigParser configParser = supplier.get();
logger.info("using json parser: {}", configParser.getClass().getSimpleName());
return configParser;
}

throw new MissingJsonParserException("unable to locate a JSON parser. "
+ "Please see <link> for more information");
}

//======== Lazy-init Holder ========//
//======== Lazy-init Holder ========//

private static class LazyHolder {
private static final ConfigParser INSTANCE = create();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2017, Optimizely and contributors
* Copyright 2016-2017, 2019, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,15 +16,89 @@
*/
package com.optimizely.ab.config.parser;

import com.optimizely.ab.config.ProjectConfig;
import com.optimizely.ab.internal.PropertyUtils;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;

import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.*;
import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validConfigJsonV4;
import static junit.framework.TestCase.fail;

/**
* Tests for {@link DefaultConfigParser}.
*/
@RunWith(Parameterized.class)
public class DefaultConfigParserTest {

@Parameterized.Parameters(name = "{index}")
public static Collection<Object[]> data() throws IOException {
return Arrays.asList(new Object[][]{
{
validConfigJsonV2(),
validProjectConfigV2()
},
{
validConfigJsonV3(),
validProjectConfigV3()
},
{
validConfigJsonV4(),
validProjectConfigV4()
}
});
}

@Parameterized.Parameter(0)
public String validDatafile;

@Parameterized.Parameter(1)
public ProjectConfig validProjectConfig;

/**
* This method is to test DefaultConfigParser when different default_parser gets set.
* For example: when optimizely_default_parser environment variable will be set to "GSON_CONFIG_PARSER" than
* "DefaultConfigParser.getInstance()" returns "GsonConfigParser" and parse ProjectConfig using it. Also
* this test will assertThat "configParser" (Provided in env variable) is instance of "GsonConfigParser.class"
*
* @throws Exception
*/
@Test
public void createThrowException() throws Exception {
// FIXME - mdodsworth: hmmm, this isn't going to be the easiest thing to test
public void testPropertyDefaultParser() throws Exception {
String defaultParser = PropertyUtils.get("default_parser");
ConfigParser configParser = DefaultConfigParser.getInstance();
ProjectConfig actual = configParser.parseProjectConfig(validDatafile);
ProjectConfig expected = validProjectConfig;
verifyProjectConfig(actual, expected);
Class expectedParser = GsonConfigParser.class;

if(defaultParser != null) {
DefaultConfigParser.ConfigParserSupplier defaultParserSupplier = DefaultConfigParser.ConfigParserSupplier.valueOf(defaultParser);
switch (defaultParserSupplier) {
case GSON_CONFIG_PARSER:
expectedParser = GsonConfigParser.class;
break;
case JACKSON_CONFIG_PARSER:
expectedParser = JacksonConfigParser.class;
break;
case JSON_CONFIG_PARSER:
expectedParser = JsonConfigParser.class;
break;
case JSON_SIMPLE_CONFIG_PARSER:
expectedParser = JsonSimpleConfigParser.class;
break;
default:
fail("Not a valid config parser");
}
}
Copy link
Contributor

@mikecdavis mikecdavis Jul 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have an else statement defaulting to the first enum that matches? not sure if this will be deterministic however, but worth a shot.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we can do that first enum that matches will be GSON_CONFIG_PARSER


Assert.assertThat(configParser, CoreMatchers.instanceOf(expectedParser));
}
}