Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 514f99b2 authored by Zhi Dou's avatar Zhi Dou Committed by Gerrit Code Review
Browse files

Merge "aconfig: add java codegen test mode"

parents 59f99b31 8ba6aa71
Loading
Loading
Loading
Loading
+146 −44
Original line number Diff line number Diff line
@@ -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,12 +112,16 @@ 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#"
    const EXPECTED_FEATUREFLAGS_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;
    public final class Flags {
        public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
@@ -134,9 +141,21 @@ mod tests {
        public static boolean enabledRw() {
            return FEATURE_FLAGS.enabledRw();
        }
            private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
        }
    "#;

    #[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 {
+8 −2
Original line number Diff line number Diff line
@@ -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> {
+10 −3
Original line number Diff line number Diff line
@@ -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()
+45 −4
Original line number Diff line number Diff line
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 }}
}

+11 −1
Original line number Diff line number Diff line
@@ -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- }};

}