use anyhow::{anyhow, bail, ensure, Context, Result}; use convert_finalized_flags::FinalizedFlagMap; use itertools::Itertools; use protobuf::Message; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; use std::fmt; use std::hash::Hasher; use std::io::Read; use std::path::PathBuf;
usecrate::codegen::cpp::generate_cpp_code; usecrate::codegen::java::{generate_java_code, JavaCodegenConfig}; usecrate::codegen::rust::generate_rust_code; usecrate::codegen::CodegenMode; usecrate::dump::{DumpFormat, DumpPredicate}; usecrate::storage::generate_storage_file; use aconfig_protos::{
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoFlagStorageBackend,
ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint,
}; use aconfig_storage_file::sip_hasher13::SipHasher13; use aconfig_storage_file::StorageFileType;
// for each mainline beta namespace, only platform and the corresponding // module containers are allowed fn supports_container(&self, pf: &ProtoParsedFlag) -> bool { matchself.namespaces.get(pf.namespace()) {
Some(setting) => {
setting.container == pf.container()
|| PLATFORM_CONTAINERS.iter().any(|&c| c == pf.container())
}
None => panic!( "Should not check container support for flags in non mainline beta namespaces"
),
}
}
fn supports_exported_mode(&self, pf: &ProtoParsedFlag) -> bool { matchself.namespaces.get(pf.namespace()) {
Some(setting) => { if setting.container == pf.container() {
setting.allow_exported
} else {
panic!("Should not check exported mode support on none mainline beta flag")
}
}
None => panic!("Should not check exported mode support on none mainline beta flag"),
}
}
}
fn assign_storage_backend(
pf: &mut ProtoParsedFlag,
beta_namespaces: &Option<MainlineBetaNamespaces>,
) -> Result<()> { let is_mainline_beta = match beta_namespaces {
Some(namespaces) => namespaces.is_mainline_beta_flag(pf),
None => false,
}; let is_read_only = pf.permission() == ProtoFlagPermission::READ_ONLY; let storage = if is_read_only {
ProtoFlagStorageBackend::NONE
} elseif is_mainline_beta {
ProtoFlagStorageBackend::DEVICE_CONFIG
} else {
ProtoFlagStorageBackend::ACONFIGD
}; let m = pf.metadata.as_mut().ok_or(anyhow!("missing metadata"))?;
m.set_storage(storage);
Ok(())
}
fn verify_mainline_beta_namespace_flag(
pf: &mut ProtoParsedFlag,
beta_namespaces: &Option<MainlineBetaNamespaces>,
) -> Result<()> { iflet Some(namespaces) = beta_namespaces { if !namespaces.has_flag(pf) { return Ok(());
}
ensure!(
namespaces.supports_container(pf), "Creating {} container flag in namespace {} is not allowed",
pf.container(),
pf.namespace()
); if pf.is_exported() {
ensure!(
namespaces.supports_exported_mode(pf), "Creating exported flag {} in namespace {} is not allowed",
pf.fully_qualified_name(),
pf.namespace()
);
}
}
Ok(())
}
// verify ParsedFlag can be added
ensure!(
parsed_flags.parsed_flag.iter().all(|other| other.name() != parsed_flag.name()), "failed to declare flag {} from {}: flag already declared",
parsed_flag.name(),
input.source
);
// add ParsedFlag to ParsedFlags
parsed_flags.parsed_flag.push(parsed_flag);
}
}
formut input in values { letmut contents = String::new();
input
.reader
.read_to_string(&mut contents)
.with_context(|| format!("failed to read {}", input.source))?; let flag_values = aconfig_protos::flag_values::try_from_text_proto(&contents)
.with_context(|| input.error_context())?; formut flag_value in flag_values.flag_value.into_iter() {
aconfig_protos::flag_value::verify_fields(&flag_value)
.with_context(|| input.error_context())?;
ensure!(
!parsed_flag.is_fixed_read_only()
|| flag_value.permission() == ProtoFlagPermission::READ_ONLY, "failed to set permission of flag {}, since this flag is fixed read only flag",
flag_value.name()
); if extended_permissions_options.force_read_only {
flag_value.set_permission(ProtoFlagPermission::READ_ONLY);
}
pubfn create_java_lib( mut input: Input,
codegen_mode: CodegenMode,
single_exported_file: bool,
allow_impl_interface_removal: bool,
finalized_flags: FinalizedFlagMap,
) -> Result<Vec<OutputFile>> { let parsed_flags = input.try_parse_flags()?; let modified_parsed_flags =
modify_parsed_flags_based_on_mode(parsed_flags.clone(), codegen_mode)?; let Some(package) = find_unique_package(&modified_parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
}; let package = package.to_string(); letmut flag_names = extract_flag_names(parsed_flags)?; let package_fingerprint = compute_flags_fingerprint(&mut flag_names); let flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?; let optimize_read_only_getter =
cfg!(optimize_read_only_java) && codegen_mode != CodegenMode::Test; let config = JavaCodegenConfig {
codegen_mode,
flag_ids,
package_fingerprint,
single_exported_file,
allow_impl_interface_removal,
finalized_flags,
support_uau_annotation: !cfg!(enable_jarjar_flags_in_framwork),
optimize_read_only_getter,
generate_checks_sdk_annotation: cfg!(feature = "generate_checks_sdk_annotation"),
};
generate_java_code(&package, modified_parsed_flags.into_iter(), config)
}
pubfn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> { // TODO(327420679): Enable export mode for native flag library
ensure!(
codegen_mode != CodegenMode::Exported, "Exported mode for generated c/c++ flag library is disabled"
); let parsed_flags = input.try_parse_flags()?; let modified_parsed_flags =
modify_parsed_flags_based_on_mode(parsed_flags.clone(), codegen_mode)?; let Some(package) = find_unique_package(&modified_parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
}; let package = package.to_string(); let flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?; let package_fingerprint: Option<u64> = if cfg!(enable_fingerprint_cpp) { letmut flag_names = extract_flag_names(parsed_flags)?;
Some(compute_flags_fingerprint(&mut flag_names))
} else {
None
};
generate_cpp_code(
&package,
modified_parsed_flags.into_iter(),
codegen_mode,
flag_ids,
package_fingerprint,
)
}
pubfn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<OutputFile> { // // TODO(327420679): Enable export mode for native flag library
ensure!(
codegen_mode != CodegenMode::Exported, "Exported mode for generated rust flag library is disabled"
); let parsed_flags = input.try_parse_flags()?; let modified_parsed_flags =
modify_parsed_flags_based_on_mode(parsed_flags.clone(), codegen_mode)?; let Some(package) = find_unique_package(&modified_parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
}; let package = package.to_string();
let package_fingerprint: Option<u64> = if cfg!(enable_fingerprint_rust) { letmut flag_names = extract_flag_names(parsed_flags)?;
Some(compute_flags_fingerprint(&mut flag_names))
} else {
None
};
pubfn assign_flag_ids<'a, I>(package: &str, parsed_flags_iter: I) -> Result<HashMap<String, u16>> where
I: Iterator<Item = &'a ProtoParsedFlag> + Clone,
{
assert!(parsed_flags_iter.clone().tuple_windows().all(|(a, b)| a.name() <= b.name())); letmut flag_ids = HashMap::new(); letmut flag_idx = 0; for pf in parsed_flags_iter { if package != pf.package() { return Err(anyhow::anyhow!("encountered a flag not in current package"));
}
// put a cap on how many flags a package can contain to 65534 if flag_idx >= u16::MAX as u32 { return Err(anyhow::anyhow!("the number of flags in a package cannot exceed 65534"));
}
if should_include_flag(pf) {
flag_ids.insert(pf.name().to_string(), flag_idx as u16);
flag_idx += 1;
}
}
Ok(flag_ids)
}
// Creates a fingerprint of the flag names (which requires sorting the vector). // Fingerprint is used by both codegen and storage files. pubfn compute_flags_fingerprint(flag_names: &mut Vec<String>) -> u64 {
flag_names.sort();
letmut hasher = SipHasher13::new(); for flag in flag_names {
hasher.write(flag.as_bytes());
}
hasher.finish()
}
// Converts ProtoParsedFlags into a vector of strings containing all of the flag // names. Helper fn for creating fingerprint for codegen files. Flags must all // belong to the same package. fn extract_flag_names(flags: ProtoParsedFlags) -> Result<Vec<String>> { let separated_flags: Vec<ProtoParsedFlag> = flags.parsed_flag.into_iter().collect::<Vec<_>>();
// All flags must belong to the same package as the fingerprint is per-package. let Some(_package) = find_unique_package(&separated_flags) else {
bail!("No parsed flags, or the parsed flags use different packages.");
};
// Check if a flag should be managed by aconfigd pubfn should_include_flag(pf: &ProtoParsedFlag) -> bool { let is_platform_container = PLATFORM_CONTAINERS.iter().any(|&c| c == pf.container()); let is_disabled_ro = pf.state == Some(ProtoFlagState::DISABLED.into())
&& pf.permission == Some(ProtoFlagPermission::READ_ONLY.into());
!(is_platform_container && is_disabled_ro)
}
#[cfg(test)] mod tests { usesuper::*; use aconfig_protos::ProtoFlagPurpose;
#[test] fn test_offset_fingerprint() { let parsed_flags = crate::test::parse_test_flags(); let expected_fingerprint: u64 = 11551379960324242360;
letmut extracted_flags = extract_flag_names(parsed_flags).unwrap(); let hash_result = compute_flags_fingerprint(&mut extracted_flags);
assert_eq!(hash_result, expected_fingerprint);
}
#[test] fn test_offset_fingerprint_matches_from_package() { let parsed_flags: ProtoParsedFlags = crate::test::parse_test_flags();
// All test flags are in the same package, so fingerprint from all of them. letmut extracted_flags = extract_flag_names(parsed_flags.clone()).unwrap(); let result_from_parsed_flags = compute_flags_fingerprint(&mutextracted_flags);
// Assert the same hash is generated for each case.
assert_eq!(result_from_parsed_flags, result_from_names);
}
#[test] fn test_offset_fingerprint_different_packages_does_not_match() { // Parse flags from two packages. let parsed_flags: ProtoParsedFlags = crate::test::parse_test_flags(); let second_parsed_flags = crate::test::parse_second_package_flags();
letmut extracted_flags = extract_flag_names(parsed_flags).unwrap(); let result_from_parsed_flags = compute_flags_fingerprint(&mutextracted_flags); letmut second_extracted_flags = extract_flag_names(second_parsed_flags).unwrap(); let second_result = compute_flags_fingerprint(&mut second_extracted_flags);
// Different flags should have a different fingerprint.
assert_ne!(result_from_parsed_flags, second_result);
}
assert_eq!(9, parsed_flags.parsed_flag.len()); for pf in parsed_flags.parsed_flag.iter() { if pf.name().starts_with("enabled_fixed_ro") { continue;
} let first = pf.trace.first().unwrap();
assert_eq!(DEFAULT_FLAG_STATE, first.state());
assert_eq!(DEFAULT_FLAG_PERMISSION, first.permission());
let last = pf.trace.last().unwrap();
assert_eq!(pf.state(), last.state());
assert_eq!(pf.permission(), last.permission());
}
#[test] fn test_parse_flags_container_mismatch_between_declaration_and_command_line() { let first_flag = r#"
package: "com.first"
container: "declaration.container"
flag {
name: "first"
namespace: "first_ns"
description: "This is the description of the first flag."
bug: "123"
} "#; let declaration =
vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
let value: Vec<Input> = vec![]; let extended_permissions_options = ExtendedPermissionsOptions {
default_permission: ProtoFlagPermission::READ_WRITE,
allow_read_write: true,
force_read_only: false,
};
let error = crate::commands::parse_flags( "com.first", "argument.container",
declaration,
value,
None,
extended_permissions_options,
)
.unwrap_err();
assert_eq!(
error.to_string(), "failed to parse memory: expected container argument.container, got declaration.container"
);
} #[test] fn test_parse_flags_no_allow_read_write_default_error() { let first_flag = r#"
package: "com.first"
container: "com.first.container"
flag {
name: "first"
namespace: "first_ns"
description: "This is the description of the first flag."
bug: "123"
} "#; let declaration =
vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }]; let extended_permissions_options = ExtendedPermissionsOptions {
default_permission: ProtoFlagPermission::READ_WRITE,
allow_read_write: false,
force_read_only: false,
};
let error = crate::commands::parse_flags( "com.first", "com.first.container",
declaration,
vec![],
None,
extended_permissions_options,
)
.unwrap_err();
assert_eq!(
error.to_string(), "flag first has permission READ_WRITE, but allow_read_write is false"
);
}
#[test] fn test_parse_flags_no_allow_read_write_value_error() { let first_flag = r#"
package: "com.first"
container: "com.first.container"
flag {
name: "first"
namespace: "first_ns"
description: "This is the description of the first flag."
bug: "123"
} "#; let declaration =
vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
let first_flag_value = r#"
flag_value {
package: "com.first"
name: "first"
state: DISABLED
permission: READ_WRITE
} "#; let value = vec![Input {
source: "memory".to_string(),
reader: Box::new(first_flag_value.as_bytes()),
}]; let extended_permissions_options = ExtendedPermissionsOptions {
default_permission: ProtoFlagPermission::READ_ONLY,
allow_read_write: false,
force_read_only: false,
}; let error = crate::commands::parse_flags( "com.first", "com.first.container",
declaration,
value,
None,
extended_permissions_options,
)
.unwrap_err();
assert_eq!(
error.to_string(), "flag first has permission READ_WRITE, but allow_read_write is false"
);
}
#[test] fn test_parse_flags_no_allow_read_write_success() { let first_flag = r#"
package: "com.first"
container: "com.first.container"
flag {
name: "first"
namespace: "first_ns"
description: "This is the description of the first flag."
bug: "123"
} "#; let declaration =
vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
let first_flag_value = r#"
flag_value {
package: "com.first"
name: "first"
state: DISABLED
permission: READ_ONLY
} "#; let value = vec![Input {
source: "memory".to_string(),
reader: Box::new(first_flag_value.as_bytes()),
}]; let extended_permissions_options = ExtendedPermissionsOptions {
default_permission: ProtoFlagPermission::READ_ONLY,
allow_read_write: false,
force_read_only: false,
}; let flags_bytes = crate::commands::parse_flags( "com.first", "com.first.container",
declaration,
value,
None,
extended_permissions_options,
)
.unwrap(); let parsed_flags =
aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
assert_eq!(ProtoFlagState::DISABLED, parsed_flag.state());
assert_eq!(ProtoFlagPermission::READ_ONLY, parsed_flag.permission());
}
#[test] fn test_parse_flags_force_read_only_convert_read_write_to_read_only_success() { let first_flag = r#"
package: "com.first"
container: "com.first.container"
flag {
name: "first"
namespace: "first_ns"
description: "This is the description of the first flag."
bug: "123"
} "#; let declaration =
vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
let first_flag_value = r#"
flag_value {
package: "com.first"
name: "first"
state: DISABLED
permission: READ_WRITE
} "#; let value = vec![Input {
source: "memory".to_string(),
reader: Box::new(first_flag_value.as_bytes()),
}]; let extended_permissions_options = ExtendedPermissionsOptions {
default_permission: ProtoFlagPermission::READ_ONLY,
allow_read_write: true,
force_read_only: true,
}; let flags_bytes = crate::commands::parse_flags( "com.first", "com.first.container",
declaration,
value,
None,
extended_permissions_options,
)
.unwrap(); let parsed_flags =
aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
assert_eq!(ProtoFlagState::DISABLED, parsed_flag.state());
assert_eq!(ProtoFlagPermission::READ_ONLY, parsed_flag.permission());
}
#[test] fn test_parse_flags_force_read_only_no_allow_read_write_does_not_fail() { let first_flag = r#"
package: "com.first"
container: "com.first.container"
flag {
name: "first"
namespace: "first_ns"
description: "This is the description of the first flag."
bug: "123"
} "#; let declaration =
vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
let first_flag_value = r#"
flag_value {
package: "com.first"
name: "first"
state: DISABLED
permission: READ_WRITE
} "#; let value = vec![Input {
source: "memory".to_string(),
reader: Box::new(first_flag_value.as_bytes()),
}]; let extended_permissions_options = ExtendedPermissionsOptions {
default_permission: ProtoFlagPermission::READ_ONLY,
allow_read_write: false,
force_read_only: true,
}; let flags_bytes = crate::commands::parse_flags( "com.first", "com.first.container",
declaration,
value,
None,
extended_permissions_options,
)
.unwrap(); let parsed_flags =
aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
assert_eq!(ProtoFlagState::DISABLED, parsed_flag.state());
assert_eq!(ProtoFlagPermission::READ_ONLY, parsed_flag.permission());
}
#[test] fn test_parse_flags_override_fixed_read_only() { let first_flag = r#"
package: "com.first"
container: "com.first.container"
flag {
name: "first"
namespace: "first_ns"
description: "This is the description of the first flag."
bug: "123"
is_fixed_read_only: true
} "#; let declaration =
vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
let first_flag_value = r#"
flag_value {
package: "com.first"
name: "first"
state: DISABLED
permission: READ_WRITE
} "#; let value = vec![Input {
source: "memory".to_string(),
reader: Box::new(first_flag_value.as_bytes()),
}]; let extended_permissions_options = ExtendedPermissionsOptions {
default_permission: ProtoFlagPermission::READ_WRITE,
allow_read_write: true,
force_read_only: false,
}; let error = crate::commands::parse_flags( "com.first", "com.first.container",
declaration,
value,
None,
extended_permissions_options,
)
.unwrap_err();
assert_eq!(
error.to_string(), "failed to set permission of flag first, since this flag is fixed read only flag"
);
}
#[test] fn test_parse_flags_metadata_purpose() { let metadata_flag = r#"
package: "com.first"
container: "test"
flag {
name: "first"
namespace: "first_ns"
description: "This is the description of this feature flag."
bug: "123"
metadata {
purpose: PURPOSE_FEATURE
}
} "#; let declaration = vec![Input {
source: "memory".to_string(),
reader: Box::new(metadata_flag.as_bytes()),
}]; let value: Vec<Input> = vec![]; let extended_permissions_options = ExtendedPermissionsOptions {
default_permission: ProtoFlagPermission::READ_ONLY,
allow_read_write: true,
force_read_only: false,
}; let flags_bytes = crate::commands::parse_flags( "com.first", "test",
declaration,
value,
None,
extended_permissions_options,
)
.unwrap(); let parsed_flags =
aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
assert_eq!(ProtoFlagPurpose::PURPOSE_FEATURE, parsed_flag.metadata.purpose());
}
// Case 1, regular RW flag without value file override let parsed_flag =
get_parsed_flag_proto("test", "com.first", metadata_flag, None, Some(config)).unwrap();
assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage());
// Case 2, regular RW flag with value file override to RO let first_flag_value = r#"
flag_value {
package: "com.first"
name: "first"
state: DISABLED
permission: READ_ONLY
} "#; let parsed_flag = get_parsed_flag_proto( "test", "com.first",
metadata_flag,
Some(first_flag_value),
Some(config),
)
.unwrap();
assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage());
// Case 3, fixed read only flag let metadata_flag = r#"
package: "com.first"
container: "test"
flag {
name: "first"
namespace: "first_ns"
description: "This is the description of this feature flag."
bug: "123"
is_fixed_read_only: true
} "#;
let parsed_flag =
get_parsed_flag_proto("test", "com.first", metadata_flag, None, Some(config)).unwrap();
assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage());
// Case 4, mainline beta namespace fixed read only flag let metadata_flag = r#"
package: "com.first"
container: "com.android.tethering"
flag {
name: "first"
namespace: "com_android_tethering"
description: "This is the description of this feature flag."
bug: "123"
is_fixed_read_only: true
} "#; let parsed_flag = get_parsed_flag_proto( "com.android.tethering", "com.first",
metadata_flag,
None,
Some(config),
)
.unwrap();
assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage());
// Case 5, mainline beta namespace platform flag let metadata_flag = r#"
package: "com.first"
container: "system"
flag {
name: "first"
namespace: "com_android_tethering"
description: "This is the description of this feature flag."
bug: "123"
} "#; let parsed_flag =
get_parsed_flag_proto("system", "com.first", metadata_flag, None, Some(config))
.unwrap();
assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage());
// Case 6, mainline beta namespace mainline flag let metadata_flag = r#"
package: "com.first"
container: "com.android.tethering"
flag {
name: "first"
namespace: "com_android_tethering"
description: "This is the description of this feature flag."
bug: "123"
} "#; let parsed_flag = get_parsed_flag_proto( "com.android.tethering", "com.first",
metadata_flag,
None,
Some(config),
)
.unwrap();
assert_eq!(ProtoFlagStorageBackend::DEVICE_CONFIG, parsed_flag.metadata.storage());
// Case 7, mainline beta namespace mainline flag but without config let metadata_flag = r#"
package: "com.first"
container: "com.android.tethering"
flag {
name: "first"
namespace: "com_android_tethering"
description: "This is the description of this feature flag."
bug: "123"
} "#; let parsed_flag =
get_parsed_flag_proto("com.android.tethering", "com.first", metadata_flag, None, None)
.unwrap();
assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage());
// Case 8, mainline beta namespace invalid container let metadata_flag = r#"
package: "com.first"
container: "com.android.tethering"
flag {
name: "first"
namespace: "com_android_networkstack"
description: "This is the description of this feature flag."
bug: "123"
} "#; let error = get_parsed_flag_proto( "com.android.tethering", "com.first",
metadata_flag,
None,
Some(config),
)
.unwrap_err();
assert_eq!(
error.to_string(), "Creating com.android.tethering container flag in namespace com_android_networkstack is not allowed"
);
// Case 9, mainline beta namespace unsupported exported mode let metadata_flag = r#"
package: "com.first"
container: "com.android.networkstack"
flag {
name: "first"
namespace: "com_android_networkstack"
description: "This is the description of this feature flag."
bug: "123"
is_exported: true
} "#; let error = get_parsed_flag_proto( "com.android.networkstack", "com.first",
metadata_flag,
None,
Some(config),
)
.unwrap_err();
assert_eq!(
error.to_string(), "Creating exported flag com.first.first in namespace com_android_networkstack is not allowed"
);
}
#[test] fn test_dump() { let input = parse_test_flags_as_input(); let bytes = dump_parsed_flags(
vec![input],
DumpFormat::Custom("{fully_qualified_name}".to_string()),
&[], false,
)
.unwrap(); let text = std::str::from_utf8(&bytes).unwrap();
assert!(text.contains("com.android.aconfig.test.disabled_ro"));
}
#[test] fn test_dump_multiple_filters() { let input = parse_test_flags_as_input(); let bytes = dump_parsed_flags(
vec![input],
DumpFormat::Custom("{fully_qualified_name}".to_string()),
&[ "container:system+state:ENABLED".to_string(), "container:system+permission:READ_WRITE".to_string(),
], false,
)
.unwrap(); let text = std::str::from_utf8(&bytes).unwrap(); let expected_flag_list = &[ "com.android.aconfig.test.disabled_rw", "com.android.aconfig.test.disabled_rw_exported", "com.android.aconfig.test.disabled_rw_in_other_namespace", "com.android.aconfig.test.enabled_fixed_ro", "com.android.aconfig.test.enabled_fixed_ro_exported", "com.android.aconfig.test.enabled_ro", "com.android.aconfig.test.enabled_ro_exported", "com.android.aconfig.test.enabled_rw",
];
assert_eq!(expected_flag_list.map(|s| format!("{s}\n")).join(""), text);
}
#[test] fn test_dump_textproto_format_dedup() { let input = parse_test_flags_as_input(); let input2 = parse_test_flags_as_input(); let bytes =
dump_parsed_flags(vec![input, input2], DumpFormat::Textproto, &[], true).unwrap(); let text = std::str::from_utf8(&bytes).unwrap();
assert_eq!(
None, crate::test::first_significant_code_diff( crate::test::TEST_FLAGS_TEXTPROTO.trim(),
text.trim()
)
);
}
fn parse_test_flags_as_input() -> Input { let parsed_flags = crate::test::parse_test_flags(); let binary_proto = parsed_flags.write_to_bytes().unwrap(); let cursor = std::io::Cursor::new(binary_proto); let reader = Box::new(cursor);
Input { source: "test.data".to_string(), reader }
}
#[test] fn test_modify_parsed_flags_based_on_mode_prod() { let parsed_flags = crate::test::parse_test_flags(); let p_parsed_flags =
modify_parsed_flags_based_on_mode(parsed_flags.clone(), CodegenMode::Production)
.unwrap();
assert_eq!(parsed_flags.parsed_flag.len(), p_parsed_flags.len()); for (i, item) in p_parsed_flags.iter().enumerate() {
assert!(parsed_flags.parsed_flag[i].eq(item));
}
}
let pf = parsed_flags.parsed_flag.iter_mut().find(|pf| pf.is_exported()).unwrap(); let m = pf.metadata.as_mut().unwrap();
m.set_storage(ProtoFlagStorageBackend::DEVICE_CONFIG);
let p_parsed_flags =
modify_parsed_flags_based_on_mode(parsed_flags, CodegenMode::Exported).unwrap();
assert_eq!(3, p_parsed_flags.len()); for flag in p_parsed_flags.iter() {
assert_eq!(ProtoFlagState::DISABLED, flag.state());
assert_eq!(ProtoFlagPermission::READ_WRITE, flag.permission());
assert_eq!(ProtoFlagStorageBackend::ACONFIGD, flag.metadata.storage());
assert!(!flag.is_fixed_read_only());
assert!(flag.is_exported());
}
letmut parsed_flags = crate::test::parse_test_flags();
parsed_flags.parsed_flag.retain(|pf| !pf.is_exported()); let error =
modify_parsed_flags_based_on_mode(parsed_flags, CodegenMode::Exported).unwrap_err();
assert_eq!(error.to_string(), "exported library contains no exported flags");
}
let pf = parsed_flags.parsed_flag.iter_mut().find(|pf| !pf.is_exported()).unwrap(); let m = pf.metadata.as_mut().unwrap();
m.set_storage(ProtoFlagStorageBackend::DEVICE_CONFIG);
let p_parsed_flags =
modify_parsed_flags_based_on_mode(parsed_flags, CodegenMode::ForceReadOnly).unwrap();
assert_eq!(6, p_parsed_flags.len()); for flag in p_parsed_flags.iter() {
assert_eq!(ProtoFlagPermission::READ_ONLY, flag.permission());
assert_eq!(ProtoFlagStorageBackend::NONE, flag.metadata.storage());
assert!(!flag.is_exported());
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.26Bemerkung:
(vorverarbeitet am 2026-06-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.