aconfig: add java codegen test mode
Add java codegen test mode. The test mode will generate Flags.java and FeatureFlagsImpl.java differently. * Flags.java will have getter and setter function to switch the FeatureFlagsImpl. Flags.java will not initialize the instance of FeatureFlagsImpl during initialization, thus it will force the user to set up the flag values for the tests. * FeatureFlagsImpl removes the dependency on DeviceConfig, and allows the caller to set the values of flags. Command changes This change adds a new parameter `mode` to `create-java-lib` subcommand. The default value of `mode` is production, which will generate files for production usage, and keeps the same behavior as before. The new `mode` test is added to trigger the test mode. The command is aconfig create-java-lib --cache=<path_to_cache> --out=<out_path> --mode=test Test: atest aconfig.test Bug: 288632682 Change-Id: I7566464eb762f3107142fe787f56b17f5be631b7
This commit is contained in:
parent
571cd07796
commit
8ba6aa71b1
5 changed files with 221 additions and 55 deletions
|
@ -20,17 +20,23 @@ use std::path::PathBuf;
|
|||
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 @@ where
|
|||
|
||||
#[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 @@ fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement {
|
|||
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 @@ mod tests {
|
|||
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 @@ mod tests {
|
|||
}
|
||||
}
|
||||
"#;
|
||||
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 {
|
||||
|
|
|
@ -129,12 +129,18 @@ pub fn parse_flags(package: &str, declarations: Vec<Input>, values: Vec<Input>)
|
|||
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> {
|
||||
|
|
|
@ -34,7 +34,7 @@ mod protos;
|
|||
#[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 @@ fn cli() -> Command {
|
|||
.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 @@ fn main() -> Result<()> {
|
|||
}
|
||||
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()
|
||||
|
|
|
@ -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 }}
|
||||
}
|
||||
|
||||
{{ 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 }}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,16 @@ public final class Flags \{
|
|||
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- }};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue