Merge "aconfig: add java codegen test mode"
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index 47516b7..15eb2d6 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -20,17 +20,23 @@
use tinytemplate::TinyTemplate;
use crate::codegen;
-use crate::commands::OutputFile;
+use crate::commands::{CodegenMode, OutputFile};
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
-pub fn generate_java_code<'a, I>(package: &str, parsed_flags_iter: I) -> Result<Vec<OutputFile>>
+pub fn generate_java_code<'a, I>(
+ package: &str,
+ parsed_flags_iter: I,
+ codegen_mode: CodegenMode,
+) -> Result<Vec<OutputFile>>
where
I: Iterator<Item = &'a ProtoParsedFlag>,
{
let class_elements: Vec<ClassElement> =
parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect();
let is_read_write = class_elements.iter().any(|elem| elem.is_read_write);
- let context = Context { package_name: package.to_string(), is_read_write, class_elements };
+ let is_test_mode = codegen_mode == CodegenMode::Test;
+ let context =
+ Context { class_elements, is_test_mode, is_read_write, package_name: package.to_string() };
let mut template = TinyTemplate::new();
template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
template.add_template(
@@ -56,14 +62,15 @@
#[derive(Serialize)]
struct Context {
- pub package_name: String,
- pub is_read_write: bool,
pub class_elements: Vec<ClassElement>,
+ pub is_test_mode: bool,
+ pub is_read_write: bool,
+ pub package_name: String,
}
#[derive(Serialize)]
struct ClassElement {
- pub default_value: String,
+ pub default_value: bool,
pub device_config_namespace: String,
pub device_config_flag: String,
pub flag_name_constant_suffix: String,
@@ -75,11 +82,7 @@
let device_config_flag = codegen::create_device_config_ident(package, pf.name())
.expect("values checked at flag parse time");
ClassElement {
- default_value: if pf.state() == ProtoFlagState::ENABLED {
- "true".to_string()
- } else {
- "false".to_string()
- },
+ default_value: pf.state() == ProtoFlagState::ENABLED,
device_config_namespace: pf.namespace().to_string(),
device_config_flag,
flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
@@ -109,34 +112,50 @@
use super::*;
use std::collections::HashMap;
- #[test]
- fn test_generate_java_code() {
- let parsed_flags = crate::test::parse_test_flags();
- let generated_files =
- generate_java_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap();
- let expect_flags_content = r#"
- package com.android.aconfig.test;
- public final class Flags {
- public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
- public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
- public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
- public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
+ const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#"
+ package com.android.aconfig.test;
+ public interface FeatureFlags {
+ boolean disabledRo();
+ boolean disabledRw();
+ boolean enabledRo();
+ boolean enabledRw();
+ }"#;
- public static boolean disabledRo() {
- return FEATURE_FLAGS.disabledRo();
- }
- public static boolean disabledRw() {
- return FEATURE_FLAGS.disabledRw();
- }
- public static boolean enabledRo() {
- return FEATURE_FLAGS.enabledRo();
- }
- public static boolean enabledRw() {
- return FEATURE_FLAGS.enabledRw();
- }
- private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+ const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
+ package com.android.aconfig.test;
+ public final class Flags {
+ public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
+ public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
+ public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
+ public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
+
+ public static boolean disabledRo() {
+ return FEATURE_FLAGS.disabledRo();
}
- "#;
+ public static boolean disabledRw() {
+ return FEATURE_FLAGS.disabledRw();
+ }
+ public static boolean enabledRo() {
+ return FEATURE_FLAGS.enabledRo();
+ }
+ public static boolean enabledRw() {
+ return FEATURE_FLAGS.enabledRw();
+ }
+ "#;
+
+ #[test]
+ fn test_generate_java_code_production() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let generated_files = generate_java_code(
+ crate::test::TEST_PACKAGE,
+ parsed_flags.parsed_flag.iter(),
+ CodegenMode::Production,
+ )
+ .unwrap();
+ let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ + r#"
+ private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+ }"#;
let expected_featureflagsimpl_content = r#"
package com.android.aconfig.test;
import android.provider.DeviceConfig;
@@ -167,19 +186,102 @@
}
}
"#;
- let expected_featureflags_content = r#"
+ 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),
+ ]);
+
+ for file in generated_files {
+ let file_path = file.path.to_str().unwrap();
+ assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
+ assert_eq!(
+ None,
+ crate::test::first_significant_code_diff(
+ file_set.get(file_path).unwrap(),
+ &String::from_utf8(file.contents.clone()).unwrap()
+ ),
+ "File {} content is not correct",
+ file_path
+ );
+ file_set.remove(file_path);
+ }
+
+ assert!(file_set.is_empty());
+ }
+
+ #[test]
+ fn test_generate_java_code_test() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let generated_files = generate_java_code(
+ crate::test::TEST_PACKAGE,
+ parsed_flags.parsed_flag.iter(),
+ CodegenMode::Test,
+ )
+ .unwrap();
+ let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ + r#"
+ public static void setFeatureFlagsImpl(FeatureFlags featureFlags) {
+ Flags.FEATURE_FLAGS = featureFlags;
+ }
+ public static void unsetFeatureFlagsImpl() {
+ Flags.FEATURE_FLAGS = null;
+ }
+ private static FeatureFlags FEATURE_FLAGS;
+ }
+ "#;
+ let expected_featureflagsimpl_content = r#"
package com.android.aconfig.test;
- public interface FeatureFlags {
- boolean disabledRo();
- boolean disabledRw();
- boolean enabledRo();
- boolean enabledRw();
+ import static java.util.stream.Collectors.toMap;
+ import java.util.stream.Stream;
+ import java.util.HashMap;
+ public final class FeatureFlagsImpl implements FeatureFlags {
+ @Override
+ public boolean disabledRo() {
+ return getFlag(Flags.FLAG_DISABLED_RO);
+ }
+ @Override
+ public boolean disabledRw() {
+ return getFlag(Flags.FLAG_DISABLED_RW);
+ }
+ @Override
+ public boolean enabledRo() {
+ return getFlag(Flags.FLAG_ENABLED_RO);
+ }
+ @Override
+ public boolean enabledRw() {
+ return getFlag(Flags.FLAG_ENABLED_RW);
+ }
+ public void setFlag(String flagName, boolean value) {
+ if (!this.mFlagMap.containsKey(flagName)) {
+ throw new IllegalArgumentException("no such flag" + flagName);
+ }
+ this.mFlagMap.put(flagName, value);
+ }
+ 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(
+ Flags.FLAG_DISABLED_RO,
+ Flags.FLAG_DISABLED_RW,
+ Flags.FLAG_ENABLED_RO,
+ Flags.FLAG_ENABLED_RW
+ )
+ .collect(
+ HashMap::new,
+ (map, elem) -> map.put(elem, null),
+ HashMap::putAll
+ );
}
"#;
let mut file_set = HashMap::from([
- ("com/android/aconfig/test/Flags.java", expect_flags_content),
+ ("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/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
]);
for file in generated_files {
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 58831cc..dd2087b 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -129,12 +129,18 @@
Ok(output)
}
-pub fn create_java_lib(mut input: Input) -> Result<Vec<OutputFile>> {
+#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
+pub enum CodegenMode {
+ Production,
+ Test,
+}
+
+pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
let parsed_flags = input.try_parse_flags()?;
let Some(package) = find_unique_package(&parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
};
- generate_java_code(package, parsed_flags.parsed_flag.iter())
+ generate_java_code(package, parsed_flags.parsed_flag.iter(), codegen_mode)
}
pub fn create_cpp_lib(mut input: Input) -> Result<OutputFile> {
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index e6a325d..e20c60c 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -34,7 +34,7 @@
#[cfg(test)]
mod test;
-use commands::{DumpFormat, Input, OutputFile};
+use commands::{CodegenMode, DumpFormat, Input, OutputFile};
fn cli() -> Command {
Command::new("aconfig")
@@ -49,7 +49,13 @@
.subcommand(
Command::new("create-java-lib")
.arg(Arg::new("cache").long("cache").required(true))
- .arg(Arg::new("out").long("out").required(true)),
+ .arg(Arg::new("out").long("out").required(true))
+ .arg(
+ Arg::new("mode")
+ .long("mode")
+ .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+ .default_value("production"),
+ ),
)
.subcommand(
Command::new("create-cpp-lib")
@@ -148,7 +154,8 @@
}
Some(("create-java-lib", sub_matches)) => {
let cache = open_single_file(sub_matches, "cache")?;
- let generated_files = commands::create_java_lib(cache)?;
+ let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
+ let generated_files = commands::create_java_lib(cache, *mode)?;
let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
generated_files
.iter()
diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template
index 2b031f1..dafd99e 100644
--- a/tools/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template
@@ -1,20 +1,61 @@
package {package_name};
-{{ if is_read_write }}
+{{ -if is_test_mode }}
+import static java.util.stream.Collectors.toMap;
+
+import java.util.stream.Stream;
+import java.util.HashMap;
+{{ else}}
+{{ 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 item.is_read_write- }}
+ {{ -if not is_test_mode- }}
+ {{ 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 }}
}
{{ endfor }}
-}
\ No newline at end of file
+
+ {{ 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);
+ }
+
+ 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 }}
+}
+
+
diff --git a/tools/aconfig/templates/Flags.java.template b/tools/aconfig/templates/Flags.java.template
index 62116c5..eef98eb 100644
--- a/tools/aconfig/templates/Flags.java.template
+++ b/tools/aconfig/templates/Flags.java.template
@@ -9,6 +9,16 @@
return FEATURE_FLAGS.{item.method_name}();
}
{{ endfor }}
- private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+ {{ if is_test_mode }}
+ public static void setFeatureFlagsImpl(FeatureFlags featureFlags) \{
+ Flags.FEATURE_FLAGS = featureFlags;
+ }
+
+ public static void unsetFeatureFlagsImpl() \{
+ Flags.FEATURE_FLAGS = null;
+ }
+ {{ -endif}}
+
+ private static FeatureFlags FEATURE_FLAGS{{ -if not is_test_mode }} = new FeatureFlagsImpl(){{ -endif- }};
}