Merge changes from topic "fakefeatureflagsimpl" into main

* changes:
  Add setFlag and resetAll in FeatureFlags test mode
  Generate FakeFeatureFlagsImpl in test mode
This commit is contained in:
Zhi Dou
2023-08-09 19:39:04 +00:00
committed by Gerrit Code Review
8 changed files with 301 additions and 63 deletions

View File

@@ -101,7 +101,7 @@ java_aconfig_library {
android_test { android_test {
name: "aconfig.test.java", name: "aconfig.test.java",
srcs: [ srcs: [
"tests/**/*.java", "tests/AconfigTest.java",
], ],
manifest: "tests/AndroidManifest.xml", manifest: "tests/AndroidManifest.xml",
certificate: "platform", certificate: "platform",
@@ -113,6 +113,25 @@ android_test {
test_suites: ["device-tests"], test_suites: ["device-tests"],
} }
java_aconfig_library {
name: "aconfig_host_test_java_library",
aconfig_declarations: "aconfig.test.flags",
host_supported: true,
test: true,
}
java_test_host {
name: "AconfigJavaHostTest",
srcs: [
"tests/AconfigHostTest.java",
],
static_libs: [
"aconfig_host_test_java_library",
"junit",
],
test_suites: ["general-tests"],
}
// integration tests: C++ // integration tests: C++
cc_aconfig_library { cc_aconfig_library {

View File

@@ -47,9 +47,13 @@ where
"FeatureFlags.java", "FeatureFlags.java",
include_str!("../templates/FeatureFlags.java.template"), include_str!("../templates/FeatureFlags.java.template"),
)?; )?;
template.add_template(
"FakeFeatureFlagsImpl.java",
include_str!("../templates/FakeFeatureFlagsImpl.java.template"),
)?;
let path: PathBuf = package.split('.').collect(); let path: PathBuf = package.split('.').collect();
["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"] ["Flags.java", "FeatureFlags.java", "FeatureFlagsImpl.java", "FakeFeatureFlagsImpl.java"]
.iter() .iter()
.map(|file| { .map(|file| {
Ok(OutputFile { Ok(OutputFile {
@@ -112,14 +116,14 @@ mod tests {
use super::*; use super::*;
use std::collections::HashMap; use std::collections::HashMap;
const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#" const EXPECTED_FEATUREFLAGS_COMMON_CONTENT: &str = r#"
package com.android.aconfig.test; package com.android.aconfig.test;
public interface FeatureFlags { public interface FeatureFlags {
boolean disabledRo(); boolean disabledRo();
boolean disabledRw(); boolean disabledRw();
boolean enabledRo(); boolean enabledRo();
boolean enabledRw(); boolean enabledRw();
}"#; "#;
const EXPECTED_FLAG_COMMON_CONTENT: &str = r#" const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
package com.android.aconfig.test; package com.android.aconfig.test;
@@ -143,6 +147,29 @@ mod tests {
} }
"#; "#;
const EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT: &str = r#"
@Override
public boolean disabledRo() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
public boolean disabledRw() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
public boolean enabledRo() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
public boolean enabledRw() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
"#;
#[test] #[test]
fn test_generate_java_code_production() { fn test_generate_java_code_production() {
let parsed_flags = crate::test::parse_test_flags(); let parsed_flags = crate::test::parse_test_flags();
@@ -152,11 +179,22 @@ mod tests {
CodegenMode::Production, CodegenMode::Production,
) )
.unwrap(); .unwrap();
let expect_featureflags_content = EXPECTED_FEATUREFLAGS_COMMON_CONTENT.to_string()
+ r#"
}"#;
let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string() let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ r#" + r#"
private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
}"#; }"#;
let expected_featureflagsimpl_content = r#" let expect_fakefeatureflagsimpl_content = r#"
package com.android.aconfig.test;
public class FakeFeatureFlagsImpl implements FeatureFlags {"#
.to_owned()
+ EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT
+ r#"
}
"#;
let expect_featureflagsimpl_content = r#"
package com.android.aconfig.test; package com.android.aconfig.test;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
public final class FeatureFlagsImpl implements FeatureFlags { public final class FeatureFlagsImpl implements FeatureFlags {
@@ -188,8 +226,12 @@ mod tests {
"#; "#;
let mut file_set = HashMap::from([ let mut file_set = HashMap::from([
("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT), ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content.as_str()),
(
"com/android/aconfig/test/FakeFeatureFlagsImpl.java",
expect_fakefeatureflagsimpl_content.as_str(),
),
]); ]);
for file in generated_files { for file in generated_files {
@@ -219,24 +261,47 @@ mod tests {
CodegenMode::Test, CodegenMode::Test,
) )
.unwrap(); .unwrap();
let expect_featureflags_content = EXPECTED_FEATUREFLAGS_COMMON_CONTENT.to_string()
+ r#"
public void setFlag(String flagName, boolean value);
public void resetAll();
}"#;
let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string() let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ r#" + r#"
public static void setFeatureFlagsImpl(FeatureFlags featureFlags) { public static void setFeatureFlags(FeatureFlags featureFlags) {
Flags.FEATURE_FLAGS = featureFlags; Flags.FEATURE_FLAGS = featureFlags;
} }
public static void unsetFeatureFlagsImpl() { public static void unsetFeatureFlags() {
Flags.FEATURE_FLAGS = null; Flags.FEATURE_FLAGS = null;
} }
private static FeatureFlags FEATURE_FLAGS; private static FeatureFlags FEATURE_FLAGS;
} }
"#; "#;
let expected_featureflagsimpl_content = r#" let expect_featureflagsimpl_content = r#"
package com.android.aconfig.test;
public final class FeatureFlagsImpl implements FeatureFlags {"#
.to_owned()
+ EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT
+ r#"
@Override
public void setFlag(String flagName, boolean value) {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
public void resetAll() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
}
"#;
let expect_fakefeatureflagsimpl_content = r#"
package com.android.aconfig.test; package com.android.aconfig.test;
import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
public final class FeatureFlagsImpl implements FeatureFlags { public class FakeFeatureFlagsImpl implements FeatureFlags {
@Override @Override
public boolean disabledRo() { public boolean disabledRo() {
return getFlag(Flags.FLAG_DISABLED_RO); return getFlag(Flags.FLAG_DISABLED_RO);
@@ -253,12 +318,14 @@ mod tests {
public boolean enabledRw() { public boolean enabledRw() {
return getFlag(Flags.FLAG_ENABLED_RW); return getFlag(Flags.FLAG_ENABLED_RW);
} }
@Override
public void setFlag(String flagName, boolean value) { public void setFlag(String flagName, boolean value) {
if (!this.mFlagMap.containsKey(flagName)) { if (!this.mFlagMap.containsKey(flagName)) {
throw new IllegalArgumentException("no such flag" + flagName); throw new IllegalArgumentException("no such flag" + flagName);
} }
this.mFlagMap.put(flagName, value); this.mFlagMap.put(flagName, value);
} }
@Override
public void resetAll() { public void resetAll() {
for (Map.Entry entry : mFlagMap.entrySet()) { for (Map.Entry entry : mFlagMap.entrySet()) {
entry.setValue(null); entry.setValue(null);
@@ -284,10 +351,18 @@ mod tests {
); );
} }
"#; "#;
let mut file_set = HashMap::from([ let mut file_set = HashMap::from([
("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content.as_str()),
("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT), (
"com/android/aconfig/test/FeatureFlagsImpl.java",
expect_featureflagsimpl_content.as_str(),
),
(
"com/android/aconfig/test/FakeFeatureFlagsImpl.java",
expect_fakefeatureflagsimpl_content,
),
]); ]);
for file in generated_files { for file in generated_files {

View File

@@ -0,0 +1,61 @@
package {package_name};
{{ if is_test_mode }}
import static java.util.stream.Collectors.toMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class FakeFeatureFlagsImpl implements FeatureFlags \{
{{ for item in class_elements}}
@Override
public boolean {item.method_name}() \{
return getFlag(Flags.FLAG_{item.flag_name_constant_suffix});
}
{{ endfor}}
@Override
public void setFlag(String flagName, boolean value) \{
if (!this.mFlagMap.containsKey(flagName)) \{
throw new IllegalArgumentException("no such flag" + flagName);
}
this.mFlagMap.put(flagName, value);
}
@Override
public void resetAll() \{
for (Map.Entry entry : mFlagMap.entrySet()) \{
entry.setValue(null);
}
}
private boolean getFlag(String flagName) \{
Boolean value = this.mFlagMap.get(flagName);
if (value == null) \{
throw new IllegalArgumentException(flagName + " is not set");
}
return value;
}
private HashMap<String, Boolean> mFlagMap = Stream.of(
{{-for item in class_elements}}
Flags.FLAG_{item.flag_name_constant_suffix}{{ if not @last }},{{ endif }}
{{ -endfor }}
)
.collect(
HashMap::new,
(map, elem) -> map.put(elem, null),
HashMap::putAll
);
}
{{ else }}
{#- Generate only stub if in prod mode #}
public class FakeFeatureFlagsImpl implements FeatureFlags \{
{{ for item in class_elements}}
@Override
public boolean {item.method_name}() \{
throw new UnsupportedOperationException(
"Method is not implemented.");
}
{{ endfor}}
}
{{ endif }}

View File

@@ -4,4 +4,10 @@ public interface FeatureFlags \{
{{ for item in class_elements}} {{ for item in class_elements}}
boolean {item.method_name}(); boolean {item.method_name}();
{{ endfor }} {{ endfor }}
{{ -if is_test_mode }}
public void setFlag(String flagName, boolean value);
public void resetAll();
{{ -endif }}
} }

View File

@@ -1,65 +1,44 @@
package {package_name}; package {package_name};
{{ -if is_test_mode }} {{ if not is_test_mode }}
import static java.util.stream.Collectors.toMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
{{ else}}
{{ if is_read_write- }} {{ if is_read_write- }}
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
{{ -endif- }}
{{ endif }} {{ endif }}
public final class FeatureFlagsImpl implements FeatureFlags \{ public final class FeatureFlagsImpl implements FeatureFlags \{
{{ for item in class_elements}} {{ for item in class_elements}}
@Override @Override
public boolean {item.method_name}() \{ public boolean {item.method_name}() \{
{{ -if not is_test_mode- }} {{ -if item.is_read_write }}
{{ if item.is_read_write }}
return DeviceConfig.getBoolean( return DeviceConfig.getBoolean(
"{item.device_config_namespace}", "{item.device_config_namespace}",
"{item.device_config_flag}", "{item.device_config_flag}",
{item.default_value} {item.default_value}
); );
{{ -else }} {{ else }}
return {item.default_value}; return {item.default_value};
{{ -endif- }} {{ endif- }}
{{ else }}
return getFlag(Flags.FLAG_{item.flag_name_constant_suffix});
{{ -endif }}
} }
{{ endfor- }} {{ endfor- }}
{{ if is_test_mode }}
public void setFlag(String flagName, boolean value) \{
if (!this.mFlagMap.containsKey(flagName)) \{
throw new IllegalArgumentException("no such flag" + flagName);
}
this.mFlagMap.put(flagName, value);
}
public void resetAll() \{
for (Map.Entry entry : mFlagMap.entrySet()) \{
entry.setValue(null);
}
}
private boolean getFlag(String flagName) \{
Boolean value = this.mFlagMap.get(flagName);
if (value == null) \{
throw new IllegalArgumentException(flagName + " is not set");
}
return value;
}
private HashMap<String, Boolean> mFlagMap = Stream.of(
{{-for item in class_elements}}
Flags.FLAG_{item.flag_name_constant_suffix}{{ if not @last }},{{ endif }}
{{ -endfor }}
)
.collect(
HashMap::new,
(map, elem) -> map.put(elem, null),
HashMap::putAll
);
{{ -endif }}
} }
{{ else }}
{#- Generate only stub if in test mode #}
public final class FeatureFlagsImpl implements FeatureFlags \{
{{ for item in class_elements}}
@Override
public boolean {item.method_name}() \{
throw new UnsupportedOperationException(
"Method is not implemented.");
}
{{ endfor- }}
@Override
public void setFlag(String flagName, boolean value) \{
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
public void resetAll() \{
throw new UnsupportedOperationException(
"Method is not implemented.");
}
}
{{ endif }}

View File

@@ -10,11 +10,11 @@ public final class Flags \{
} }
{{ endfor }} {{ endfor }}
{{ -if is_test_mode }} {{ -if is_test_mode }}
public static void setFeatureFlagsImpl(FeatureFlags featureFlags) \{ public static void setFeatureFlags(FeatureFlags featureFlags) \{
Flags.FEATURE_FLAGS = featureFlags; Flags.FEATURE_FLAGS = featureFlags;
} }
public static void unsetFeatureFlagsImpl() \{ public static void unsetFeatureFlags() \{
Flags.FEATURE_FLAGS = null; Flags.FEATURE_FLAGS = null;
} }
{{ endif}} {{ endif}}

View File

@@ -0,0 +1,88 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.android.aconfig.test.FakeFeatureFlagsImpl;
import com.android.aconfig.test.FeatureFlags;
import com.android.aconfig.test.FeatureFlagsImpl;
import com.android.aconfig.test.Flags;
@RunWith(JUnit4.class)
public final class AconfigHostTest {
@Test
public void testThrowsExceptionIfFlagNotSet() {
assertThrows(NullPointerException.class, () -> Flags.disabledRo());
FeatureFlags featureFlags = new FakeFeatureFlagsImpl();
assertThrows(IllegalArgumentException.class, () -> featureFlags.disabledRo());
}
@Test
public void testSetFlagInFakeFeatureFlagsImpl() {
FeatureFlags featureFlags = new FakeFeatureFlagsImpl();
featureFlags.setFlag(Flags.FLAG_ENABLED_RW, true);
assertTrue(featureFlags.enabledRw());
featureFlags.setFlag(Flags.FLAG_ENABLED_RW, false);
assertFalse(featureFlags.enabledRw());
//Set Flags
assertThrows(NullPointerException.class, () -> Flags.enabledRw());
Flags.setFeatureFlags(featureFlags);
featureFlags.setFlag(Flags.FLAG_ENABLED_RW, true);
assertTrue(Flags.enabledRw());
Flags.unsetFeatureFlags();
}
@Test
public void testSetFlagWithRandomName() {
FeatureFlags featureFlags = new FakeFeatureFlagsImpl();
assertThrows(IllegalArgumentException.class,
() -> featureFlags.setFlag("Randome_name", true));
}
@Test
public void testResetFlagsInFakeFeatureFlagsImpl() {
FeatureFlags featureFlags = new FakeFeatureFlagsImpl();
featureFlags.setFlag(Flags.FLAG_ENABLED_RO, true);
assertTrue(featureFlags.enabledRo());
featureFlags.resetAll();
assertThrows(IllegalArgumentException.class, () -> featureFlags.enabledRo());
// Set value after reset
featureFlags.setFlag(Flags.FLAG_ENABLED_RO, false);
assertFalse(featureFlags.enabledRo());
}
@Test
public void testFlagsSetFeatureFlags() {
FeatureFlags featureFlags = new FakeFeatureFlagsImpl();
featureFlags.setFlag(Flags.FLAG_ENABLED_RW, true);
assertThrows(NullPointerException.class, () -> Flags.enabledRw());
Flags.setFeatureFlags(featureFlags);
assertTrue(Flags.enabledRw());
Flags.unsetFeatureFlags();
}
@Test
public void testFlagsUnsetFeatureFlags() {
FeatureFlags featureFlags = new FakeFeatureFlagsImpl();
featureFlags.setFlag(Flags.FLAG_ENABLED_RW, true);
assertThrows(NullPointerException.class, () -> Flags.enabledRw());
Flags.setFeatureFlags(featureFlags);
assertTrue(Flags.enabledRw());
Flags.unsetFeatureFlags();
assertThrows(NullPointerException.class, () -> Flags.enabledRw());
}
@Test
public void testFeatureFlagsImplNotImpl() {
FeatureFlags featureFlags = new FeatureFlagsImpl();
assertThrows(UnsupportedOperationException.class,
() -> featureFlags.enabledRw());
}
}

View File

@@ -8,12 +8,16 @@ import static com.android.aconfig.test.Flags.enabledRo;
import static com.android.aconfig.test.Flags.enabledRw; import static com.android.aconfig.test.Flags.enabledRw;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
import com.android.aconfig.test.FakeFeatureFlagsImpl;
import com.android.aconfig.test.FeatureFlags;
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public final class AconfigTest { public final class AconfigTest {
@Test @Test
@@ -43,4 +47,10 @@ public final class AconfigTest {
// (currently all flags are assigned the default READ_ONLY + DISABLED) // (currently all flags are assigned the default READ_ONLY + DISABLED)
assertFalse(enabledRw()); assertFalse(enabledRw());
} }
@Test
public void testFakeFeatureFlagsImplNotImpl() {
FeatureFlags featureFlags = new FakeFeatureFlagsImpl();
assertThrows(UnsupportedOperationException.class, () -> featureFlags.enabledRw());
}
} }