From 5aaeee37495f3532198cca1679b85ba44cbf6bb6 Mon Sep 17 00:00:00 2001 From: Zhi Dou Date: Mon, 7 Aug 2023 22:54:13 +0000 Subject: [PATCH 1/2] Generate FakeFeatureFlagsImpl in test mode Before FeatureFlagsImpl will be used as a fake for testing. This change adds new class FakeFeatureFlagsImpl. The FeatureFlagsImpl will keep the same as production. FakeFeatureFlagsImpl can be used as fake for testing. FakeFeatureFlagsImpl, and FeatureFlagsImpl will be generated in both test and prod mode. In test mode FeatureFlagsImpl will just be a stub, and in prod mode FakeFeatureFlagsImpl will just be a stub. Bug: 280833463 Test: atest aconfig.test Change-Id: I11c1e716a9ea00d55600e5e9d5fb6442420762e6 --- tools/aconfig/src/codegen_java.rs | 71 ++++++++++++++++--- .../FakeFeatureFlagsImpl.java.template | 59 +++++++++++++++ .../templates/FeatureFlagsImpl.java.template | 64 +++++------------ tools/aconfig/templates/Flags.java.template | 4 +- 4 files changed, 140 insertions(+), 58 deletions(-) create mode 100644 tools/aconfig/templates/FakeFeatureFlagsImpl.java.template diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs index 8ab6ffa8f7..a11737034c 100644 --- a/tools/aconfig/src/codegen_java.rs +++ b/tools/aconfig/src/codegen_java.rs @@ -47,9 +47,13 @@ where "FeatureFlags.java", include_str!("../templates/FeatureFlags.java.template"), )?; + template.add_template( + "FakeFeatureFlagsImpl.java", + include_str!("../templates/FakeFeatureFlagsImpl.java.template"), + )?; let path: PathBuf = package.split('.').collect(); - ["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"] + ["Flags.java", "FeatureFlags.java", "FeatureFlagsImpl.java", "FakeFeatureFlagsImpl.java"] .iter() .map(|file| { Ok(OutputFile { @@ -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] fn test_generate_java_code_production() { let parsed_flags = crate::test::parse_test_flags(); @@ -156,7 +183,15 @@ mod tests { + r#" 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; import android.provider.DeviceConfig; public final class FeatureFlagsImpl implements FeatureFlags { @@ -188,8 +223,12 @@ mod tests { "#; let mut file_set = HashMap::from([ ("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/FakeFeatureFlagsImpl.java", + expect_fakefeatureflagsimpl_content.as_str(), + ), ]); for file in generated_files { @@ -221,22 +260,30 @@ mod tests { .unwrap(); let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string() + r#" - public static void setFeatureFlagsImpl(FeatureFlags featureFlags) { + public static void setFeatureFlags(FeatureFlags featureFlags) { Flags.FEATURE_FLAGS = featureFlags; } - public static void unsetFeatureFlagsImpl() { + public static void unsetFeatureFlags() { Flags.FEATURE_FLAGS = null; } 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#" + } + "#; + let expect_fakefeatureflagsimpl_content = r#" package com.android.aconfig.test; import static java.util.stream.Collectors.toMap; import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; - public final class FeatureFlagsImpl implements FeatureFlags { + public class FakeFeatureFlagsImpl implements FeatureFlags { @Override public boolean disabledRo() { return getFlag(Flags.FLAG_DISABLED_RO); @@ -284,10 +331,18 @@ mod tests { ); } "#; + let mut file_set = HashMap::from([ ("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", 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 { diff --git a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template new file mode 100644 index 0000000000..db35d28f68 --- /dev/null +++ b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template @@ -0,0 +1,59 @@ +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}} + 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 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 }} diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template index 082d476de5..ba86ce552c 100644 --- a/tools/aconfig/templates/FeatureFlagsImpl.java.template +++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template @@ -1,65 +1,33 @@ 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; -{{ else}} +{{ if not is_test_mode }} {{ if is_read_write- }} import android.provider.DeviceConfig; -{{ -endif- }} {{ endif }} public final class FeatureFlagsImpl implements FeatureFlags \{ {{ for item in class_elements}} @Override public boolean {item.method_name}() \{ - {{ -if not is_test_mode- }} - {{ if item.is_read_write }} + {{ -if item.is_read_write }} return DeviceConfig.getBoolean( "{item.device_config_namespace}", "{item.device_config_flag}", {item.default_value} ); - {{ -else }} + {{ else }} return {item.default_value}; - {{ -endif- }} - {{ else }} - return getFlag(Flags.FLAG_{item.flag_name_constant_suffix}); - {{ -endif }} + {{ endif- }} } {{ 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 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- }} +} +{{ endif }} diff --git a/tools/aconfig/templates/Flags.java.template b/tools/aconfig/templates/Flags.java.template index c244b15156..012eba6660 100644 --- a/tools/aconfig/templates/Flags.java.template +++ b/tools/aconfig/templates/Flags.java.template @@ -10,11 +10,11 @@ public final class Flags \{ } {{ endfor }} {{ -if is_test_mode }} - public static void setFeatureFlagsImpl(FeatureFlags featureFlags) \{ + public static void setFeatureFlags(FeatureFlags featureFlags) \{ Flags.FEATURE_FLAGS = featureFlags; } - public static void unsetFeatureFlagsImpl() \{ + public static void unsetFeatureFlags() \{ Flags.FEATURE_FLAGS = null; } {{ endif}} From a7200115c55fbed28e1362aedbcf428c3a616cc0 Mon Sep 17 00:00:00 2001 From: Zhi Dou Date: Mon, 7 Aug 2023 18:09:28 +0000 Subject: [PATCH 2/2] Add setFlag and resetAll in FeatureFlags test mode Add methods setFlag and resetAll in FeatureFlags in test mode. For the injection usecase, user will use the interface FeatureFlags in the code to control the flags. Add tests for test mode. Bug: 280833463 Test: Atest AconfigJavaHostTest --host Change-Id: Ib59ba35a9011a6400af42fc9c283d37193577997 --- tools/aconfig/Android.bp | 21 ++++- tools/aconfig/src/codegen_java.rs | 28 +++++- .../FakeFeatureFlagsImpl.java.template | 2 + .../templates/FeatureFlags.java.template | 6 ++ .../templates/FeatureFlagsImpl.java.template | 11 +++ tools/aconfig/tests/AconfigHostTest.java | 88 +++++++++++++++++++ tools/aconfig/tests/AconfigTest.java | 10 +++ 7 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 tools/aconfig/tests/AconfigHostTest.java diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp index a4ea7f4552..93fd6f73fc 100644 --- a/tools/aconfig/Android.bp +++ b/tools/aconfig/Android.bp @@ -101,7 +101,7 @@ java_aconfig_library { android_test { name: "aconfig.test.java", srcs: [ - "tests/**/*.java", + "tests/AconfigTest.java", ], manifest: "tests/AndroidManifest.xml", certificate: "platform", @@ -113,6 +113,25 @@ android_test { 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++ cc_aconfig_library { diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs index a11737034c..2c9dcf5a48 100644 --- a/tools/aconfig/src/codegen_java.rs +++ b/tools/aconfig/src/codegen_java.rs @@ -116,14 +116,14 @@ mod tests { use super::*; use std::collections::HashMap; - const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#" + const EXPECTED_FEATUREFLAGS_COMMON_CONTENT: &str = r#" package com.android.aconfig.test; public interface FeatureFlags { boolean disabledRo(); boolean disabledRw(); boolean enabledRo(); boolean enabledRw(); - }"#; + "#; const EXPECTED_FLAG_COMMON_CONTENT: &str = r#" package com.android.aconfig.test; @@ -179,6 +179,9 @@ mod tests { CodegenMode::Production, ) .unwrap(); + let expect_featureflags_content = EXPECTED_FEATUREFLAGS_COMMON_CONTENT.to_string() + + r#" + }"#; let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string() + r#" private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); @@ -224,7 +227,7 @@ mod tests { let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), ("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(), @@ -258,6 +261,11 @@ mod tests { CodegenMode::Test, ) .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() + r#" public static void setFeatureFlags(FeatureFlags featureFlags) { @@ -275,6 +283,16 @@ mod tests { .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#" @@ -300,12 +318,14 @@ mod tests { public boolean enabledRw() { return getFlag(Flags.FLAG_ENABLED_RW); } + @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); @@ -334,7 +354,7 @@ mod tests { let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), - ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT), + ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content.as_str()), ( "com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content.as_str(), diff --git a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template index db35d28f68..dba82ef6fa 100644 --- a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template +++ b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template @@ -13,6 +13,7 @@ public class FakeFeatureFlagsImpl implements FeatureFlags \{ 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); @@ -20,6 +21,7 @@ public class FakeFeatureFlagsImpl implements FeatureFlags \{ this.mFlagMap.put(flagName, value); } + @Override public void resetAll() \{ for (Map.Entry entry : mFlagMap.entrySet()) \{ entry.setValue(null); diff --git a/tools/aconfig/templates/FeatureFlags.java.template b/tools/aconfig/templates/FeatureFlags.java.template index e0f201fc88..c99ccbb97a 100644 --- a/tools/aconfig/templates/FeatureFlags.java.template +++ b/tools/aconfig/templates/FeatureFlags.java.template @@ -4,4 +4,10 @@ public interface FeatureFlags \{ {{ for item in class_elements}} boolean {item.method_name}(); {{ endfor }} + +{{ -if is_test_mode }} + public void setFlag(String flagName, boolean value); + + public void resetAll(); +{{ -endif }} } diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template index ba86ce552c..7e1eb151f8 100644 --- a/tools/aconfig/templates/FeatureFlagsImpl.java.template +++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template @@ -29,5 +29,16 @@ public final class FeatureFlagsImpl implements FeatureFlags \{ "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 }} diff --git a/tools/aconfig/tests/AconfigHostTest.java b/tools/aconfig/tests/AconfigHostTest.java new file mode 100644 index 0000000000..29a01e30c8 --- /dev/null +++ b/tools/aconfig/tests/AconfigHostTest.java @@ -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()); + } +} diff --git a/tools/aconfig/tests/AconfigTest.java b/tools/aconfig/tests/AconfigTest.java index 6681f320c1..317289de3d 100644 --- a/tools/aconfig/tests/AconfigTest.java +++ b/tools/aconfig/tests/AconfigTest.java @@ -8,12 +8,16 @@ import static com.android.aconfig.test.Flags.enabledRo; import static com.android.aconfig.test.Flags.enabledRw; import static org.junit.Assert.assertEquals; 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; + @RunWith(JUnit4.class) public final class AconfigTest { @Test @@ -43,4 +47,10 @@ public final class AconfigTest { // (currently all flags are assigned the default READ_ONLY + DISABLED) assertFalse(enabledRw()); } + + @Test + public void testFakeFeatureFlagsImplNotImpl() { + FeatureFlags featureFlags = new FakeFeatureFlagsImpl(); + assertThrows(UnsupportedOperationException.class, () -> featureFlags.enabledRw()); + } }