Merge changes from topic "aconfig-dump-format-and-filter-args" into main am: 9c80abc3ec
am: c910aa0e13
Original change: https://android-review.googlesource.com/c/platform/build/+/2878034 Change-Id: Ifebe47f4e48eaf89556c8275bd30ce0047b853cb Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -23,12 +23,12 @@ use std::path::PathBuf;
|
|||||||
use crate::codegen::cpp::generate_cpp_code;
|
use crate::codegen::cpp::generate_cpp_code;
|
||||||
use crate::codegen::java::generate_java_code;
|
use crate::codegen::java::generate_java_code;
|
||||||
use crate::codegen::rust::generate_rust_code;
|
use crate::codegen::rust::generate_rust_code;
|
||||||
use crate::storage::generate_storage_files;
|
use crate::dump::{DumpFormat, DumpPredicate};
|
||||||
|
|
||||||
use crate::protos::{
|
use crate::protos::{
|
||||||
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
|
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
|
||||||
ProtoParsedFlags, ProtoTracepoint,
|
ProtoParsedFlags, ProtoTracepoint,
|
||||||
};
|
};
|
||||||
|
use crate::storage::generate_storage_files;
|
||||||
|
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
@@ -279,73 +279,28 @@ pub fn create_device_config_sysprops(mut input: Input) -> Result<Vec<u8>> {
|
|||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
|
|
||||||
pub enum DumpFormat {
|
|
||||||
Text,
|
|
||||||
Verbose,
|
|
||||||
Protobuf,
|
|
||||||
Textproto,
|
|
||||||
Bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dump_parsed_flags(
|
pub fn dump_parsed_flags(
|
||||||
mut input: Vec<Input>,
|
mut input: Vec<Input>,
|
||||||
format: DumpFormat,
|
format: DumpFormat,
|
||||||
|
filters: &[&str],
|
||||||
dedup: bool,
|
dedup: bool,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
let individually_parsed_flags: Result<Vec<ProtoParsedFlags>> =
|
let individually_parsed_flags: Result<Vec<ProtoParsedFlags>> =
|
||||||
input.iter_mut().map(|i| i.try_parse_flags()).collect();
|
input.iter_mut().map(|i| i.try_parse_flags()).collect();
|
||||||
let parsed_flags: ProtoParsedFlags =
|
let parsed_flags: ProtoParsedFlags =
|
||||||
crate::protos::parsed_flags::merge(individually_parsed_flags?, dedup)?;
|
crate::protos::parsed_flags::merge(individually_parsed_flags?, dedup)?;
|
||||||
|
let filters: Vec<Box<DumpPredicate>> = if filters.is_empty() {
|
||||||
let mut output = Vec::new();
|
vec![Box::new(|_| true)]
|
||||||
match format {
|
} else {
|
||||||
DumpFormat::Text => {
|
filters
|
||||||
for parsed_flag in parsed_flags.parsed_flag.into_iter() {
|
.iter()
|
||||||
let line = format!(
|
.map(|f| crate::dump::create_filter_predicate(f))
|
||||||
"{} [{}]: {:?} + {:?}\n",
|
.collect::<Result<Vec<_>>>()?
|
||||||
parsed_flag.fully_qualified_name(),
|
};
|
||||||
parsed_flag.container(),
|
crate::dump::dump_parsed_flags(
|
||||||
parsed_flag.permission(),
|
parsed_flags.parsed_flag.into_iter().filter(|flag| filters.iter().any(|p| p(flag))),
|
||||||
parsed_flag.state()
|
format,
|
||||||
);
|
)
|
||||||
output.extend_from_slice(line.as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DumpFormat::Verbose => {
|
|
||||||
for parsed_flag in parsed_flags.parsed_flag.into_iter() {
|
|
||||||
let sources: Vec<_> =
|
|
||||||
parsed_flag.trace.iter().map(|tracepoint| tracepoint.source()).collect();
|
|
||||||
let line = format!(
|
|
||||||
"{} [{}]: {:?} + {:?} ({})\n",
|
|
||||||
parsed_flag.fully_qualified_name(),
|
|
||||||
parsed_flag.container(),
|
|
||||||
parsed_flag.permission(),
|
|
||||||
parsed_flag.state(),
|
|
||||||
sources.join(", ")
|
|
||||||
);
|
|
||||||
output.extend_from_slice(line.as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DumpFormat::Protobuf => {
|
|
||||||
parsed_flags.write_to_vec(&mut output)?;
|
|
||||||
}
|
|
||||||
DumpFormat::Textproto => {
|
|
||||||
let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
|
|
||||||
output.extend_from_slice(s.as_bytes());
|
|
||||||
}
|
|
||||||
DumpFormat::Bool => {
|
|
||||||
for parsed_flag in parsed_flags.parsed_flag.into_iter() {
|
|
||||||
let line = format!(
|
|
||||||
"{}={:?}\n",
|
|
||||||
parsed_flag.fully_qualified_name(),
|
|
||||||
parsed_flag.state() == ProtoFlagState::ENABLED
|
|
||||||
);
|
|
||||||
output.extend_from_slice(line.as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_unique_package(parsed_flags: &[ProtoParsedFlag]) -> Option<&str> {
|
fn find_unique_package(parsed_flags: &[ProtoParsedFlag]) -> Option<&str> {
|
||||||
@@ -622,43 +577,25 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dump_text_format() {
|
fn test_dump() {
|
||||||
let input = parse_test_flags_as_input();
|
let input = parse_test_flags_as_input();
|
||||||
let bytes = dump_parsed_flags(vec![input], DumpFormat::Text, false).unwrap();
|
let bytes = dump_parsed_flags(
|
||||||
let text = std::str::from_utf8(&bytes).unwrap();
|
vec![input],
|
||||||
assert!(
|
DumpFormat::Custom("{fully_qualified_name}".to_string()),
|
||||||
text.contains("com.android.aconfig.test.disabled_ro [system]: READ_ONLY + DISABLED")
|
&[],
|
||||||
);
|
false,
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_dump_protobuf_format() {
|
|
||||||
let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
|
|
||||||
crate::test::TEST_FLAGS_TEXTPROTO,
|
|
||||||
)
|
)
|
||||||
.unwrap()
|
|
||||||
.write_to_bytes()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let input = parse_test_flags_as_input();
|
|
||||||
let actual = dump_parsed_flags(vec![input], DumpFormat::Protobuf, false).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(expected, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_dump_textproto_format() {
|
|
||||||
let input = parse_test_flags_as_input();
|
|
||||||
let bytes = dump_parsed_flags(vec![input], DumpFormat::Textproto, false).unwrap();
|
|
||||||
let text = std::str::from_utf8(&bytes).unwrap();
|
let text = std::str::from_utf8(&bytes).unwrap();
|
||||||
assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
|
assert!(text.contains("com.android.aconfig.test.disabled_ro"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dump_textproto_format_dedup() {
|
fn test_dump_textproto_format_dedup() {
|
||||||
let input = parse_test_flags_as_input();
|
let input = parse_test_flags_as_input();
|
||||||
let input2 = 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 bytes =
|
||||||
|
dump_parsed_flags(vec![input, input2], DumpFormat::Textproto, &[], true).unwrap();
|
||||||
let text = std::str::from_utf8(&bytes).unwrap();
|
let text = std::str::from_utf8(&bytes).unwrap();
|
||||||
assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
|
assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
|
||||||
}
|
}
|
||||||
|
414
tools/aconfig/src/dump.rs
Normal file
414
tools/aconfig/src/dump.rs
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::protos::{
|
||||||
|
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoTracepoint,
|
||||||
|
};
|
||||||
|
use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
|
||||||
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
use protobuf::Message;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum DumpFormat {
|
||||||
|
Protobuf,
|
||||||
|
Textproto,
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for DumpFormat {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
// protobuf formats
|
||||||
|
"protobuf" => Ok(Self::Protobuf),
|
||||||
|
"textproto" => Ok(Self::Textproto),
|
||||||
|
|
||||||
|
// old formats now implemented as aliases to custom format
|
||||||
|
"text" => Ok(Self::Custom(
|
||||||
|
"{fully_qualified_name} [{container}]: {permission} + {state}".to_owned(),
|
||||||
|
)),
|
||||||
|
"verbose" => Ok(Self::Custom(
|
||||||
|
"{fully_qualified_name} [{container}]: {permission} + {state} ({trace:paths})"
|
||||||
|
.to_owned(),
|
||||||
|
)),
|
||||||
|
"bool" => Ok(Self::Custom("{fully_qualified_name}={state:bool}".to_owned())),
|
||||||
|
|
||||||
|
// custom format
|
||||||
|
_ => Ok(Self::Custom(value.to_owned())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_parsed_flags<I>(parsed_flags_iter: I, format: DumpFormat) -> Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = ProtoParsedFlag>,
|
||||||
|
{
|
||||||
|
let mut output = Vec::new();
|
||||||
|
match format {
|
||||||
|
DumpFormat::Protobuf => {
|
||||||
|
let parsed_flags =
|
||||||
|
ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
|
||||||
|
parsed_flags.write_to_vec(&mut output)?;
|
||||||
|
}
|
||||||
|
DumpFormat::Textproto => {
|
||||||
|
let parsed_flags =
|
||||||
|
ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
|
||||||
|
let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
|
||||||
|
output.extend_from_slice(s.as_bytes());
|
||||||
|
}
|
||||||
|
DumpFormat::Custom(format) => {
|
||||||
|
for flag in parsed_flags_iter {
|
||||||
|
dump_custom_format(&flag, &format, &mut output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_custom_format(flag: &ProtoParsedFlag, format: &str, output: &mut Vec<u8>) {
|
||||||
|
fn format_trace(trace: &[ProtoTracepoint]) -> String {
|
||||||
|
trace
|
||||||
|
.iter()
|
||||||
|
.map(|tracepoint| {
|
||||||
|
format!(
|
||||||
|
"{}: {:?} + {:?}",
|
||||||
|
tracepoint.source(),
|
||||||
|
tracepoint.permission(),
|
||||||
|
tracepoint.state()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_trace_paths(trace: &[ProtoTracepoint]) -> String {
|
||||||
|
trace.iter().map(|tracepoint| tracepoint.source()).collect::<Vec<_>>().join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_metadata(metadata: &ProtoFlagMetadata) -> String {
|
||||||
|
format!("{:?}", metadata.purpose())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut str = format
|
||||||
|
// ProtoParsedFlag fields
|
||||||
|
.replace("{package}", flag.package())
|
||||||
|
.replace("{name}", flag.name())
|
||||||
|
.replace("{namespace}", flag.namespace())
|
||||||
|
.replace("{description}", flag.description())
|
||||||
|
.replace("{bug}", &flag.bug.join(", "))
|
||||||
|
.replace("{state}", &format!("{:?}", flag.state()))
|
||||||
|
.replace("{state:bool}", &format!("{}", flag.state() == ProtoFlagState::ENABLED))
|
||||||
|
.replace("{permission}", &format!("{:?}", flag.permission()))
|
||||||
|
.replace("{trace}", &format_trace(&flag.trace))
|
||||||
|
.replace("{trace:paths}", &format_trace_paths(&flag.trace))
|
||||||
|
.replace("{is_fixed_read_only}", &format!("{}", flag.is_fixed_read_only()))
|
||||||
|
.replace("{is_exported}", &format!("{}", flag.is_exported()))
|
||||||
|
.replace("{container}", flag.container())
|
||||||
|
.replace("{metadata}", &format_metadata(&flag.metadata))
|
||||||
|
// ParsedFlagExt functions
|
||||||
|
.replace("{fully_qualified_name}", &flag.fully_qualified_name());
|
||||||
|
str.push('\n');
|
||||||
|
output.extend_from_slice(str.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DumpPredicate = dyn Fn(&ProtoParsedFlag) -> bool;
|
||||||
|
|
||||||
|
pub fn create_filter_predicate(filter: &str) -> Result<Box<DumpPredicate>> {
|
||||||
|
let predicates = filter
|
||||||
|
.split('+')
|
||||||
|
.map(|sub_filter| create_filter_predicate_single(sub_filter))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
Ok(Box::new(move |flag| predicates.iter().all(|p| p(flag))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_filter_predicate_single(filter: &str) -> Result<Box<DumpPredicate>> {
|
||||||
|
fn enum_from_str<T>(expected: &[T], s: &str) -> Result<T>
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug + Copy,
|
||||||
|
{
|
||||||
|
for candidate in expected.iter() {
|
||||||
|
if s == format!("{:?}", candidate) {
|
||||||
|
return Ok(*candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let expected =
|
||||||
|
expected.iter().map(|state| format!("{:?}", state)).collect::<Vec<_>>().join(", ");
|
||||||
|
bail!("\"{s}\": not a valid flag state, expected one of {expected}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_msg = format!("\"{filter}\": filter syntax error");
|
||||||
|
let (what, arg) = filter.split_once(':').ok_or_else(|| anyhow!(error_msg.clone()))?;
|
||||||
|
match what {
|
||||||
|
"package" => {
|
||||||
|
let expected = arg.to_owned();
|
||||||
|
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.package() == expected))
|
||||||
|
}
|
||||||
|
"name" => {
|
||||||
|
let expected = arg.to_owned();
|
||||||
|
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.name() == expected))
|
||||||
|
}
|
||||||
|
"namespace" => {
|
||||||
|
let expected = arg.to_owned();
|
||||||
|
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.namespace() == expected))
|
||||||
|
}
|
||||||
|
// description: not supported yet
|
||||||
|
"bug" => {
|
||||||
|
let expected = arg.to_owned();
|
||||||
|
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.bug.join(", ") == expected))
|
||||||
|
}
|
||||||
|
"state" => {
|
||||||
|
let expected = enum_from_str(&[ProtoFlagState::ENABLED, ProtoFlagState::DISABLED], arg)
|
||||||
|
.context(error_msg)?;
|
||||||
|
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.state() == expected))
|
||||||
|
}
|
||||||
|
"permission" => {
|
||||||
|
let expected = enum_from_str(
|
||||||
|
&[ProtoFlagPermission::READ_ONLY, ProtoFlagPermission::READ_WRITE],
|
||||||
|
arg,
|
||||||
|
)
|
||||||
|
.context(error_msg)?;
|
||||||
|
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.permission() == expected))
|
||||||
|
}
|
||||||
|
// trace: not supported yet
|
||||||
|
"is_fixed_read_only" => {
|
||||||
|
let expected: bool = arg.parse().context(error_msg)?;
|
||||||
|
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_fixed_read_only() == expected))
|
||||||
|
}
|
||||||
|
"is_exported" => {
|
||||||
|
let expected: bool = arg.parse().context(error_msg)?;
|
||||||
|
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_exported() == expected))
|
||||||
|
}
|
||||||
|
"container" => {
|
||||||
|
let expected = arg.to_owned();
|
||||||
|
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.container() == expected))
|
||||||
|
}
|
||||||
|
// metadata: not supported yet
|
||||||
|
_ => Err(anyhow!(error_msg)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::protos::ProtoParsedFlags;
|
||||||
|
use crate::test::parse_test_flags;
|
||||||
|
use protobuf::Message;
|
||||||
|
|
||||||
|
fn parse_enabled_ro_flag() -> ProtoParsedFlag {
|
||||||
|
parse_test_flags().parsed_flag.into_iter().find(|pf| pf.name() == "enabled_ro").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dumpformat_from_str() {
|
||||||
|
// supported format types
|
||||||
|
assert_eq!(DumpFormat::try_from("protobuf").unwrap(), DumpFormat::Protobuf);
|
||||||
|
assert_eq!(DumpFormat::try_from("textproto").unwrap(), DumpFormat::Textproto);
|
||||||
|
assert_eq!(
|
||||||
|
DumpFormat::try_from("foobar").unwrap(),
|
||||||
|
DumpFormat::Custom("foobar".to_owned())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dump_parsed_flags_protobuf_format() {
|
||||||
|
let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
|
||||||
|
crate::test::TEST_FLAGS_TEXTPROTO,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.write_to_bytes()
|
||||||
|
.unwrap();
|
||||||
|
let parsed_flags = parse_test_flags();
|
||||||
|
let actual =
|
||||||
|
dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Protobuf).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dump_parsed_flags_textproto_format() {
|
||||||
|
let parsed_flags = parse_test_flags();
|
||||||
|
let bytes =
|
||||||
|
dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Textproto).unwrap();
|
||||||
|
let text = std::str::from_utf8(&bytes).unwrap();
|
||||||
|
assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dump_parsed_flags_custom_format() {
|
||||||
|
macro_rules! assert_dump_parsed_flags_custom_format_contains {
|
||||||
|
($format:expr, $expected:expr) => {
|
||||||
|
let parsed_flags = parse_test_flags();
|
||||||
|
let bytes = dump_parsed_flags(
|
||||||
|
parsed_flags.parsed_flag.into_iter(),
|
||||||
|
$format.try_into().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let text = std::str::from_utf8(&bytes).unwrap();
|
||||||
|
assert!(text.contains($expected));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom format
|
||||||
|
assert_dump_parsed_flags_custom_format_contains!(
|
||||||
|
"{fully_qualified_name}={permission} + {state}",
|
||||||
|
"com.android.aconfig.test.enabled_ro=READ_ONLY + ENABLED"
|
||||||
|
);
|
||||||
|
|
||||||
|
// aliases
|
||||||
|
assert_dump_parsed_flags_custom_format_contains!(
|
||||||
|
"text",
|
||||||
|
"com.android.aconfig.test.enabled_ro [system]: READ_ONLY + ENABLED"
|
||||||
|
);
|
||||||
|
assert_dump_parsed_flags_custom_format_contains!(
|
||||||
|
"verbose",
|
||||||
|
"com.android.aconfig.test.enabled_ro [system]: READ_ONLY + ENABLED (tests/test.aconfig, tests/first.values, tests/second.values)"
|
||||||
|
);
|
||||||
|
assert_dump_parsed_flags_custom_format_contains!(
|
||||||
|
"bool",
|
||||||
|
"com.android.aconfig.test.enabled_ro=true"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dump_custom_format() {
|
||||||
|
macro_rules! assert_custom_format {
|
||||||
|
($format:expr, $expected:expr) => {
|
||||||
|
let flag = parse_enabled_ro_flag();
|
||||||
|
let mut bytes = vec![];
|
||||||
|
dump_custom_format(&flag, $format, &mut bytes);
|
||||||
|
let text = std::str::from_utf8(&bytes).unwrap();
|
||||||
|
assert_eq!(text, $expected);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_custom_format!("{package}", "com.android.aconfig.test\n");
|
||||||
|
assert_custom_format!("{name}", "enabled_ro\n");
|
||||||
|
assert_custom_format!("{namespace}", "aconfig_test\n");
|
||||||
|
assert_custom_format!("{description}", "This flag is ENABLED + READ_ONLY\n");
|
||||||
|
assert_custom_format!("{bug}", "abc\n");
|
||||||
|
assert_custom_format!("{state}", "ENABLED\n");
|
||||||
|
assert_custom_format!("{state:bool}", "true\n");
|
||||||
|
assert_custom_format!("{permission}", "READ_ONLY\n");
|
||||||
|
assert_custom_format!("{trace}", "tests/test.aconfig: READ_WRITE + DISABLED, tests/first.values: READ_WRITE + DISABLED, tests/second.values: READ_ONLY + ENABLED\n");
|
||||||
|
assert_custom_format!(
|
||||||
|
"{trace:paths}",
|
||||||
|
"tests/test.aconfig, tests/first.values, tests/second.values\n"
|
||||||
|
);
|
||||||
|
assert_custom_format!("{is_fixed_read_only}", "false\n");
|
||||||
|
assert_custom_format!("{is_exported}", "false\n");
|
||||||
|
assert_custom_format!("{container}", "system\n");
|
||||||
|
assert_custom_format!("{metadata}", "PURPOSE_BUGFIX\n");
|
||||||
|
|
||||||
|
assert_custom_format!("name={name}|state={state}", "name=enabled_ro|state=ENABLED\n");
|
||||||
|
assert_custom_format!("{state}{state}{state}", "ENABLEDENABLEDENABLED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_filter_predicate() {
|
||||||
|
macro_rules! assert_create_filter_predicate {
|
||||||
|
($filter:expr, $expected:expr) => {
|
||||||
|
let parsed_flags = parse_test_flags();
|
||||||
|
let predicate = create_filter_predicate($filter).unwrap();
|
||||||
|
let mut filtered_flags: Vec<String> = parsed_flags
|
||||||
|
.parsed_flag
|
||||||
|
.into_iter()
|
||||||
|
.filter(predicate)
|
||||||
|
.map(|flag| flag.fully_qualified_name())
|
||||||
|
.collect();
|
||||||
|
filtered_flags.sort();
|
||||||
|
assert_eq!(&filtered_flags, $expected);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_create_filter_predicate!(
|
||||||
|
"package:com.android.aconfig.test",
|
||||||
|
&[
|
||||||
|
"com.android.aconfig.test.disabled_ro",
|
||||||
|
"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_ro",
|
||||||
|
"com.android.aconfig.test.enabled_ro_exported",
|
||||||
|
"com.android.aconfig.test.enabled_rw",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_create_filter_predicate!(
|
||||||
|
"name:disabled_rw",
|
||||||
|
&["com.android.aconfig.test.disabled_rw"]
|
||||||
|
);
|
||||||
|
assert_create_filter_predicate!(
|
||||||
|
"namespace:other_namespace",
|
||||||
|
&["com.android.aconfig.test.disabled_rw_in_other_namespace"]
|
||||||
|
);
|
||||||
|
// description: not supported yet
|
||||||
|
assert_create_filter_predicate!("bug:123", &["com.android.aconfig.test.disabled_ro",]);
|
||||||
|
assert_create_filter_predicate!(
|
||||||
|
"state:ENABLED",
|
||||||
|
&[
|
||||||
|
"com.android.aconfig.test.enabled_fixed_ro",
|
||||||
|
"com.android.aconfig.test.enabled_ro",
|
||||||
|
"com.android.aconfig.test.enabled_ro_exported",
|
||||||
|
"com.android.aconfig.test.enabled_rw",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_create_filter_predicate!(
|
||||||
|
"permission:READ_ONLY",
|
||||||
|
&[
|
||||||
|
"com.android.aconfig.test.disabled_ro",
|
||||||
|
"com.android.aconfig.test.enabled_fixed_ro",
|
||||||
|
"com.android.aconfig.test.enabled_ro",
|
||||||
|
"com.android.aconfig.test.enabled_ro_exported",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
// trace: not supported yet
|
||||||
|
assert_create_filter_predicate!(
|
||||||
|
"is_fixed_read_only:true",
|
||||||
|
&["com.android.aconfig.test.enabled_fixed_ro"]
|
||||||
|
);
|
||||||
|
assert_create_filter_predicate!(
|
||||||
|
"is_exported:true",
|
||||||
|
&[
|
||||||
|
"com.android.aconfig.test.disabled_rw_exported",
|
||||||
|
"com.android.aconfig.test.enabled_ro_exported",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_create_filter_predicate!(
|
||||||
|
"container:system",
|
||||||
|
&[
|
||||||
|
"com.android.aconfig.test.disabled_ro",
|
||||||
|
"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_ro",
|
||||||
|
"com.android.aconfig.test.enabled_ro_exported",
|
||||||
|
"com.android.aconfig.test.enabled_rw",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
// metadata: not supported yet
|
||||||
|
|
||||||
|
// multiple sub filters
|
||||||
|
assert_create_filter_predicate!(
|
||||||
|
"permission:READ_ONLY+state:ENABLED",
|
||||||
|
&[
|
||||||
|
"com.android.aconfig.test.enabled_fixed_ro",
|
||||||
|
"com.android.aconfig.test.enabled_ro",
|
||||||
|
"com.android.aconfig.test.enabled_ro_exported",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -26,13 +26,21 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
mod codegen;
|
mod codegen;
|
||||||
mod commands;
|
mod commands;
|
||||||
|
mod dump;
|
||||||
mod protos;
|
mod protos;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|
||||||
|
use dump::DumpFormat;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
use commands::{CodegenMode, DumpFormat, Input, OutputFile};
|
use commands::{CodegenMode, Input, OutputFile};
|
||||||
|
|
||||||
|
const HELP_DUMP_FILTER: &str = r#"
|
||||||
|
Limit which flags to output. If multiple --filter arguments are provided, the output will be
|
||||||
|
limited to flags that match any of the filters.
|
||||||
|
"#;
|
||||||
|
|
||||||
fn cli() -> Command {
|
fn cli() -> Command {
|
||||||
Command::new("aconfig")
|
Command::new("aconfig")
|
||||||
@@ -103,9 +111,10 @@ fn cli() -> Command {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::new("format")
|
Arg::new("format")
|
||||||
.long("format")
|
.long("format")
|
||||||
.value_parser(EnumValueParser::<commands::DumpFormat>::new())
|
.value_parser(|s: &str| DumpFormat::try_from(s))
|
||||||
.default_value("text"),
|
.default_value("text"),
|
||||||
)
|
)
|
||||||
|
.arg(Arg::new("filter").long("filter").action(ArgAction::Append).help(HELP_DUMP_FILTER.trim()))
|
||||||
.arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
|
.arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
|
||||||
.arg(Arg::new("out").long("out").default_value("-")),
|
.arg(Arg::new("out").long("out").default_value("-")),
|
||||||
)
|
)
|
||||||
@@ -249,8 +258,13 @@ fn main() -> Result<()> {
|
|||||||
let input = open_zero_or_more_files(sub_matches, "cache")?;
|
let input = open_zero_or_more_files(sub_matches, "cache")?;
|
||||||
let format = get_required_arg::<DumpFormat>(sub_matches, "format")
|
let format = get_required_arg::<DumpFormat>(sub_matches, "format")
|
||||||
.context("failed to dump previously parsed flags")?;
|
.context("failed to dump previously parsed flags")?;
|
||||||
|
let filters = sub_matches
|
||||||
|
.get_many::<String>("filter")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.map(String::as_ref)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let dedup = get_required_arg::<bool>(sub_matches, "dedup")?;
|
let dedup = get_required_arg::<bool>(sub_matches, "dedup")?;
|
||||||
let output = commands::dump_parsed_flags(input, *format, *dedup)?;
|
let output = commands::dump_parsed_flags(input, format.clone(), &filters, *dedup)?;
|
||||||
let path = get_required_arg::<String>(sub_matches, "out")?;
|
let path = get_required_arg::<String>(sub_matches, "out")?;
|
||||||
write_output_to_file_or_stdout(path, &output)?;
|
write_output_to_file_or_stdout(path, &output)?;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user