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:
@@ -20,17 +20,23 @@ use std::path::PathBuf;
|
|||||||
use tinytemplate::TinyTemplate;
|
use tinytemplate::TinyTemplate;
|
||||||
|
|
||||||
use crate::codegen;
|
use crate::codegen;
|
||||||
use crate::commands::OutputFile;
|
use crate::commands::{CodegenMode, OutputFile};
|
||||||
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
|
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
|
where
|
||||||
I: Iterator<Item = &'a ProtoParsedFlag>,
|
I: Iterator<Item = &'a ProtoParsedFlag>,
|
||||||
{
|
{
|
||||||
let class_elements: Vec<ClassElement> =
|
let class_elements: Vec<ClassElement> =
|
||||||
parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect();
|
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 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();
|
let mut template = TinyTemplate::new();
|
||||||
template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
|
template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
|
||||||
template.add_template(
|
template.add_template(
|
||||||
@@ -56,14 +62,15 @@ where
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Context {
|
struct Context {
|
||||||
pub package_name: String,
|
|
||||||
pub is_read_write: bool,
|
|
||||||
pub class_elements: Vec<ClassElement>,
|
pub class_elements: Vec<ClassElement>,
|
||||||
|
pub is_test_mode: bool,
|
||||||
|
pub is_read_write: bool,
|
||||||
|
pub package_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ClassElement {
|
struct ClassElement {
|
||||||
pub default_value: String,
|
pub default_value: bool,
|
||||||
pub device_config_namespace: String,
|
pub device_config_namespace: String,
|
||||||
pub device_config_flag: String,
|
pub device_config_flag: String,
|
||||||
pub flag_name_constant_suffix: 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())
|
let device_config_flag = codegen::create_device_config_ident(package, pf.name())
|
||||||
.expect("values checked at flag parse time");
|
.expect("values checked at flag parse time");
|
||||||
ClassElement {
|
ClassElement {
|
||||||
default_value: if pf.state() == ProtoFlagState::ENABLED {
|
default_value: pf.state() == ProtoFlagState::ENABLED,
|
||||||
"true".to_string()
|
|
||||||
} else {
|
|
||||||
"false".to_string()
|
|
||||||
},
|
|
||||||
device_config_namespace: pf.namespace().to_string(),
|
device_config_namespace: pf.namespace().to_string(),
|
||||||
device_config_flag,
|
device_config_flag,
|
||||||
flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
|
flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
|
||||||
@@ -109,34 +112,50 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[test]
|
const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#"
|
||||||
fn test_generate_java_code() {
|
package com.android.aconfig.test;
|
||||||
let parsed_flags = crate::test::parse_test_flags();
|
public interface FeatureFlags {
|
||||||
let generated_files =
|
boolean disabledRo();
|
||||||
generate_java_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap();
|
boolean disabledRw();
|
||||||
let expect_flags_content = r#"
|
boolean enabledRo();
|
||||||
package com.android.aconfig.test;
|
boolean enabledRw();
|
||||||
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() {
|
const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
|
||||||
return FEATURE_FLAGS.disabledRo();
|
package com.android.aconfig.test;
|
||||||
}
|
public final class Flags {
|
||||||
public static boolean disabledRw() {
|
public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
|
||||||
return FEATURE_FLAGS.disabledRw();
|
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 boolean enabledRo() {
|
public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
|
||||||
return FEATURE_FLAGS.enabledRo();
|
|
||||||
}
|
public static boolean disabledRo() {
|
||||||
public static boolean enabledRw() {
|
return FEATURE_FLAGS.disabledRo();
|
||||||
return FEATURE_FLAGS.enabledRw();
|
|
||||||
}
|
|
||||||
private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
|
|
||||||
}
|
}
|
||||||
"#;
|
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#"
|
let expected_featureflagsimpl_content = r#"
|
||||||
package com.android.aconfig.test;
|
package com.android.aconfig.test;
|
||||||
import android.provider.DeviceConfig;
|
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;
|
package com.android.aconfig.test;
|
||||||
public interface FeatureFlags {
|
import static java.util.stream.Collectors.toMap;
|
||||||
boolean disabledRo();
|
import java.util.stream.Stream;
|
||||||
boolean disabledRw();
|
import java.util.HashMap;
|
||||||
boolean enabledRo();
|
public final class FeatureFlagsImpl implements FeatureFlags {
|
||||||
boolean enabledRw();
|
@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([
|
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/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 {
|
for file in generated_files {
|
||||||
|
@@ -129,12 +129,18 @@ pub fn parse_flags(package: &str, declarations: Vec<Input>, values: Vec<Input>)
|
|||||||
Ok(output)
|
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 parsed_flags = input.try_parse_flags()?;
|
||||||
let Some(package) = find_unique_package(&parsed_flags) else {
|
let Some(package) = find_unique_package(&parsed_flags) else {
|
||||||
bail!("no parsed flags, or the parsed flags use different packages");
|
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> {
|
pub fn create_cpp_lib(mut input: Input) -> Result<OutputFile> {
|
||||||
|
@@ -34,7 +34,7 @@ mod protos;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
use commands::{DumpFormat, Input, OutputFile};
|
use commands::{CodegenMode, DumpFormat, Input, OutputFile};
|
||||||
|
|
||||||
fn cli() -> Command {
|
fn cli() -> Command {
|
||||||
Command::new("aconfig")
|
Command::new("aconfig")
|
||||||
@@ -49,7 +49,13 @@ fn cli() -> Command {
|
|||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new("create-java-lib")
|
Command::new("create-java-lib")
|
||||||
.arg(Arg::new("cache").long("cache").required(true))
|
.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(
|
.subcommand(
|
||||||
Command::new("create-cpp-lib")
|
Command::new("create-cpp-lib")
|
||||||
@@ -148,7 +154,8 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Some(("create-java-lib", sub_matches)) => {
|
Some(("create-java-lib", sub_matches)) => {
|
||||||
let cache = open_single_file(sub_matches, "cache")?;
|
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")?);
|
let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
|
||||||
generated_files
|
generated_files
|
||||||
.iter()
|
.iter()
|
||||||
|
@@ -1,20 +1,61 @@
|
|||||||
package {package_name};
|
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;
|
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 item.is_read_write- }}
|
{{ -if not is_test_mode- }}
|
||||||
|
{{ 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- }}
|
||||||
|
{{ else }}
|
||||||
|
return getFlag(Flags.FLAG_{item.flag_name_constant_suffix});
|
||||||
{{ -endif }}
|
{{ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}();
|
return FEATURE_FLAGS.{item.method_name}();
|
||||||
}
|
}
|
||||||
{{ endfor }}
|
{{ 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- }};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user