1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //! `aconfig_storage_file` is a crate that defines aconfig storage file format, it
18 //! also includes apis to read flags from storage files. It provides three apis to
19 //! interface with storage files:
20 //!
21 //! 1, function to get package flag value start offset
22 //! pub fn get_package_offset(container: &str, package: &str) -> `Result<Option<PackageOffset>>>`
23 //!
24 //! 2, function to get flag offset within a specific package
25 //! pub fn get_flag_offset(container: &str, package_id: u32, flag: &str) -> `Result<Option<u16>>>`
26 //!
27 //! 3, function to get the actual flag value given the global offset (combined package and
28 //! flag offset).
29 //! pub fn get_boolean_flag_value(container: &str, offset: u32) -> `Result<bool>`
30 //!
31 //! Note these are low level apis that are expected to be only used in auto generated flag
32 //! apis. DO NOT DIRECTLY USE THESE APIS IN YOUR SOURCE CODE. For auto generated flag apis
33 //! please refer to the g3doc go/android-flags
34
35 pub mod flag_info;
36 pub mod flag_table;
37 pub mod flag_value;
38 pub mod package_table;
39 pub mod protos;
40 pub mod test_utils;
41
42 use anyhow::anyhow;
43 use std::cmp::Ordering;
44 use std::collections::hash_map::DefaultHasher;
45 use std::fs::File;
46 use std::hash::{Hash, Hasher};
47 use std::io::Read;
48
49 pub use crate::flag_info::{FlagInfoBit, FlagInfoHeader, FlagInfoList, FlagInfoNode};
50 pub use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
51 pub use crate::flag_value::{FlagValueHeader, FlagValueList};
52 pub use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
53
54 use crate::AconfigStorageError::{
55 BytesParseFail, HashTableSizeLimit, InvalidFlagValueType, InvalidStoredFlagType,
56 };
57
58 /// Storage file version
59 pub const FILE_VERSION: u32 = 1;
60
61 /// Good hash table prime number
62 pub(crate) const HASH_PRIMES: [u32; 29] = [
63 7, 17, 29, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241,
64 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611,
65 402653189, 805306457, 1610612741,
66 ];
67
68 /// Storage file type enum
69 #[derive(Clone, Debug, PartialEq, Eq)]
70 pub enum StorageFileType {
71 PackageMap = 0,
72 FlagMap = 1,
73 FlagVal = 2,
74 FlagInfo = 3,
75 }
76
77 impl TryFrom<&str> for StorageFileType {
78 type Error = anyhow::Error;
79
try_from(value: &str) -> std::result::Result<Self, Self::Error>80 fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
81 match value {
82 "package_map" => Ok(Self::PackageMap),
83 "flag_map" => Ok(Self::FlagMap),
84 "flag_val" => Ok(Self::FlagVal),
85 "flag_info" => Ok(Self::FlagInfo),
86 _ => Err(anyhow!(
87 "Invalid storage file type, valid types are package_map|flag_map|flag_val|flag_info"
88 )),
89 }
90 }
91 }
92
93 impl TryFrom<u8> for StorageFileType {
94 type Error = anyhow::Error;
95
try_from(value: u8) -> Result<Self, Self::Error>96 fn try_from(value: u8) -> Result<Self, Self::Error> {
97 match value {
98 x if x == Self::PackageMap as u8 => Ok(Self::PackageMap),
99 x if x == Self::FlagMap as u8 => Ok(Self::FlagMap),
100 x if x == Self::FlagVal as u8 => Ok(Self::FlagVal),
101 x if x == Self::FlagInfo as u8 => Ok(Self::FlagInfo),
102 _ => Err(anyhow!("Invalid storage file type")),
103 }
104 }
105 }
106
107 /// Flag type enum as stored by storage file
108 /// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16.
109 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
110 pub enum StoredFlagType {
111 ReadWriteBoolean = 0,
112 ReadOnlyBoolean = 1,
113 FixedReadOnlyBoolean = 2,
114 }
115
116 impl TryFrom<u16> for StoredFlagType {
117 type Error = AconfigStorageError;
118
try_from(value: u16) -> Result<Self, Self::Error>119 fn try_from(value: u16) -> Result<Self, Self::Error> {
120 match value {
121 x if x == Self::ReadWriteBoolean as u16 => Ok(Self::ReadWriteBoolean),
122 x if x == Self::ReadOnlyBoolean as u16 => Ok(Self::ReadOnlyBoolean),
123 x if x == Self::FixedReadOnlyBoolean as u16 => Ok(Self::FixedReadOnlyBoolean),
124 _ => Err(InvalidStoredFlagType(anyhow!("Invalid stored flag type"))),
125 }
126 }
127 }
128
129 /// Flag value type enum, one FlagValueType maps to many StoredFlagType
130 /// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16
131 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
132 pub enum FlagValueType {
133 Boolean = 0,
134 }
135
136 impl TryFrom<StoredFlagType> for FlagValueType {
137 type Error = AconfigStorageError;
138
try_from(value: StoredFlagType) -> Result<Self, Self::Error>139 fn try_from(value: StoredFlagType) -> Result<Self, Self::Error> {
140 match value {
141 StoredFlagType::ReadWriteBoolean => Ok(Self::Boolean),
142 StoredFlagType::ReadOnlyBoolean => Ok(Self::Boolean),
143 StoredFlagType::FixedReadOnlyBoolean => Ok(Self::Boolean),
144 }
145 }
146 }
147
148 impl TryFrom<u16> for FlagValueType {
149 type Error = AconfigStorageError;
150
try_from(value: u16) -> Result<Self, Self::Error>151 fn try_from(value: u16) -> Result<Self, Self::Error> {
152 match value {
153 x if x == Self::Boolean as u16 => Ok(Self::Boolean),
154 _ => Err(InvalidFlagValueType(anyhow!("Invalid flag value type"))),
155 }
156 }
157 }
158
159 /// Storage query api error
160 #[non_exhaustive]
161 #[derive(thiserror::Error, Debug)]
162 pub enum AconfigStorageError {
163 #[error("failed to read the file")]
164 FileReadFail(#[source] anyhow::Error),
165
166 #[error("fail to parse protobuf")]
167 ProtobufParseFail(#[source] anyhow::Error),
168
169 #[error("storage files not found for this container")]
170 StorageFileNotFound(#[source] anyhow::Error),
171
172 #[error("fail to map storage file")]
173 MapFileFail(#[source] anyhow::Error),
174
175 #[error("fail to get mapped file")]
176 ObtainMappedFileFail(#[source] anyhow::Error),
177
178 #[error("fail to flush mapped storage file")]
179 MapFlushFail(#[source] anyhow::Error),
180
181 #[error("number of items in hash table exceed limit")]
182 HashTableSizeLimit(#[source] anyhow::Error),
183
184 #[error("failed to parse bytes into data")]
185 BytesParseFail(#[source] anyhow::Error),
186
187 #[error("cannot parse storage files with a higher version")]
188 HigherStorageFileVersion(#[source] anyhow::Error),
189
190 #[error("invalid storage file byte offset")]
191 InvalidStorageFileOffset(#[source] anyhow::Error),
192
193 #[error("failed to create file")]
194 FileCreationFail(#[source] anyhow::Error),
195
196 #[error("invalid stored flag type")]
197 InvalidStoredFlagType(#[source] anyhow::Error),
198
199 #[error("invalid flag value type")]
200 InvalidFlagValueType(#[source] anyhow::Error),
201 }
202
203 /// Get the right hash table size given number of entries in the table. Use a
204 /// load factor of 0.5 for performance.
get_table_size(entries: u32) -> Result<u32, AconfigStorageError>205 pub fn get_table_size(entries: u32) -> Result<u32, AconfigStorageError> {
206 HASH_PRIMES
207 .iter()
208 .find(|&&num| num >= 2 * entries)
209 .copied()
210 .ok_or(HashTableSizeLimit(anyhow!("Number of items in a hash table exceeds limit")))
211 }
212
213 /// Get the corresponding bucket index given the key and number of buckets
get_bucket_index<T: Hash>(val: &T, num_buckets: u32) -> u32214 pub(crate) fn get_bucket_index<T: Hash>(val: &T, num_buckets: u32) -> u32 {
215 let mut s = DefaultHasher::new();
216 val.hash(&mut s);
217 (s.finish() % num_buckets as u64) as u32
218 }
219
220 /// Read and parse bytes as u8
read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError>221 pub fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError> {
222 let val =
223 u8::from_le_bytes(buf[*head..*head + 1].try_into().map_err(|errmsg| {
224 BytesParseFail(anyhow!("fail to parse u8 from bytes: {}", errmsg))
225 })?);
226 *head += 1;
227 Ok(val)
228 }
229
230 /// Read and parse bytes as u16
read_u16_from_bytes( buf: &[u8], head: &mut usize, ) -> Result<u16, AconfigStorageError>231 pub(crate) fn read_u16_from_bytes(
232 buf: &[u8],
233 head: &mut usize,
234 ) -> Result<u16, AconfigStorageError> {
235 let val =
236 u16::from_le_bytes(buf[*head..*head + 2].try_into().map_err(|errmsg| {
237 BytesParseFail(anyhow!("fail to parse u16 from bytes: {}", errmsg))
238 })?);
239 *head += 2;
240 Ok(val)
241 }
242
243 /// Read and parse bytes as u32
read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32, AconfigStorageError>244 pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32, AconfigStorageError> {
245 let val =
246 u32::from_le_bytes(buf[*head..*head + 4].try_into().map_err(|errmsg| {
247 BytesParseFail(anyhow!("fail to parse u32 from bytes: {}", errmsg))
248 })?);
249 *head += 4;
250 Ok(val)
251 }
252
253 /// Read and parse bytes as string
read_str_from_bytes( buf: &[u8], head: &mut usize, ) -> Result<String, AconfigStorageError>254 pub(crate) fn read_str_from_bytes(
255 buf: &[u8],
256 head: &mut usize,
257 ) -> Result<String, AconfigStorageError> {
258 let num_bytes = read_u32_from_bytes(buf, head)? as usize;
259 let val = String::from_utf8(buf[*head..*head + num_bytes].to_vec())
260 .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse string from bytes: {}", errmsg)))?;
261 *head += num_bytes;
262 Ok(val)
263 }
264
265 /// Read in storage file as bytes
read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageError>266 pub fn read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageError> {
267 let mut file = File::open(file_path).map_err(|errmsg| {
268 AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
269 })?;
270 let mut buffer = Vec::new();
271 file.read_to_end(&mut buffer).map_err(|errmsg| {
272 AconfigStorageError::FileReadFail(anyhow!(
273 "Failed to read bytes from file {}: {}",
274 file_path,
275 errmsg
276 ))
277 })?;
278 Ok(buffer)
279 }
280
281 /// Flag value summary
282 #[derive(Debug, PartialEq)]
283 pub struct FlagValueSummary {
284 pub package_name: String,
285 pub flag_name: String,
286 pub flag_value: String,
287 pub value_type: StoredFlagType,
288 }
289
290 /// List flag values from storage files
list_flags( package_map: &str, flag_map: &str, flag_val: &str, ) -> Result<Vec<FlagValueSummary>, AconfigStorageError>291 pub fn list_flags(
292 package_map: &str,
293 flag_map: &str,
294 flag_val: &str,
295 ) -> Result<Vec<FlagValueSummary>, AconfigStorageError> {
296 let package_table = PackageTable::from_bytes(&read_file_to_bytes(package_map)?)?;
297 let flag_table = FlagTable::from_bytes(&read_file_to_bytes(flag_map)?)?;
298 let flag_value_list = FlagValueList::from_bytes(&read_file_to_bytes(flag_val)?)?;
299
300 let mut package_info = vec![("", 0); package_table.header.num_packages as usize];
301 for node in package_table.nodes.iter() {
302 package_info[node.package_id as usize] = (&node.package_name, node.boolean_start_index);
303 }
304
305 let mut flags = Vec::new();
306 for node in flag_table.nodes.iter() {
307 let (package_name, boolean_start_index) = package_info[node.package_id as usize];
308 let flag_index = boolean_start_index + node.flag_index as u32;
309 let flag_value = flag_value_list.booleans[flag_index as usize];
310 flags.push(FlagValueSummary {
311 package_name: String::from(package_name),
312 flag_name: node.flag_name.clone(),
313 flag_value: flag_value.to_string(),
314 value_type: node.flag_type,
315 });
316 }
317
318 flags.sort_by(|v1, v2| match v1.package_name.cmp(&v2.package_name) {
319 Ordering::Equal => v1.flag_name.cmp(&v2.flag_name),
320 other => other,
321 });
322 Ok(flags)
323 }
324
325 /// Flag value and info summary
326 #[derive(Debug, PartialEq)]
327 pub struct FlagValueAndInfoSummary {
328 pub package_name: String,
329 pub flag_name: String,
330 pub flag_value: String,
331 pub value_type: StoredFlagType,
332 pub is_readwrite: bool,
333 pub has_server_override: bool,
334 pub has_local_override: bool,
335 }
336
337 /// List flag values and info from storage files
list_flags_with_info( package_map: &str, flag_map: &str, flag_val: &str, flag_info: &str, ) -> Result<Vec<FlagValueAndInfoSummary>, AconfigStorageError>338 pub fn list_flags_with_info(
339 package_map: &str,
340 flag_map: &str,
341 flag_val: &str,
342 flag_info: &str,
343 ) -> Result<Vec<FlagValueAndInfoSummary>, AconfigStorageError> {
344 let package_table = PackageTable::from_bytes(&read_file_to_bytes(package_map)?)?;
345 let flag_table = FlagTable::from_bytes(&read_file_to_bytes(flag_map)?)?;
346 let flag_value_list = FlagValueList::from_bytes(&read_file_to_bytes(flag_val)?)?;
347 let flag_info = FlagInfoList::from_bytes(&read_file_to_bytes(flag_info)?)?;
348
349 let mut package_info = vec![("", 0); package_table.header.num_packages as usize];
350 for node in package_table.nodes.iter() {
351 package_info[node.package_id as usize] = (&node.package_name, node.boolean_start_index);
352 }
353
354 let mut flags = Vec::new();
355 for node in flag_table.nodes.iter() {
356 let (package_name, boolean_start_index) = package_info[node.package_id as usize];
357 let flag_index = boolean_start_index + node.flag_index as u32;
358 let flag_value = flag_value_list.booleans[flag_index as usize];
359 let flag_attribute = flag_info.nodes[flag_index as usize].attributes;
360 flags.push(FlagValueAndInfoSummary {
361 package_name: String::from(package_name),
362 flag_name: node.flag_name.clone(),
363 flag_value: flag_value.to_string(),
364 value_type: node.flag_type,
365 is_readwrite: flag_attribute & (FlagInfoBit::IsReadWrite as u8) != 0,
366 has_server_override: flag_attribute & (FlagInfoBit::HasServerOverride as u8) != 0,
367 has_local_override: flag_attribute & (FlagInfoBit::HasLocalOverride as u8) != 0,
368 });
369 }
370
371 flags.sort_by(|v1, v2| match v1.package_name.cmp(&v2.package_name) {
372 Ordering::Equal => v1.flag_name.cmp(&v2.flag_name),
373 other => other,
374 });
375 Ok(flags)
376 }
377
378 // *************************************** //
379 // CC INTERLOP
380 // *************************************** //
381
382 // Exported rust data structure and methods, c++ code will be generated
383 #[cxx::bridge]
384 mod ffi {
385 /// flag value summary cxx return
386 pub struct FlagValueSummaryCXX {
387 pub package_name: String,
388 pub flag_name: String,
389 pub flag_value: String,
390 pub value_type: String,
391 }
392
393 /// flag value and info summary cxx return
394 pub struct FlagValueAndInfoSummaryCXX {
395 pub package_name: String,
396 pub flag_name: String,
397 pub flag_value: String,
398 pub value_type: String,
399 pub is_readwrite: bool,
400 pub has_server_override: bool,
401 pub has_local_override: bool,
402 }
403
404 /// list flag result cxx return
405 pub struct ListFlagValueResultCXX {
406 pub query_success: bool,
407 pub error_message: String,
408 pub flags: Vec<FlagValueSummaryCXX>,
409 }
410
411 /// list flag with info result cxx return
412 pub struct ListFlagValueAndInfoResultCXX {
413 pub query_success: bool,
414 pub error_message: String,
415 pub flags: Vec<FlagValueAndInfoSummaryCXX>,
416 }
417
418 // Rust export to c++
419 extern "Rust" {
list_flags_cxx( package_map: &str, flag_map: &str, flag_val: &str, ) -> ListFlagValueResultCXX420 pub fn list_flags_cxx(
421 package_map: &str,
422 flag_map: &str,
423 flag_val: &str,
424 ) -> ListFlagValueResultCXX;
425
list_flags_with_info_cxx( package_map: &str, flag_map: &str, flag_val: &str, flag_info: &str, ) -> ListFlagValueAndInfoResultCXX426 pub fn list_flags_with_info_cxx(
427 package_map: &str,
428 flag_map: &str,
429 flag_val: &str,
430 flag_info: &str,
431 ) -> ListFlagValueAndInfoResultCXX;
432 }
433 }
434
435 /// implement flag value summary cxx return type
436 impl ffi::FlagValueSummaryCXX {
new(summary: FlagValueSummary) -> Self437 pub(crate) fn new(summary: FlagValueSummary) -> Self {
438 Self {
439 package_name: summary.package_name,
440 flag_name: summary.flag_name,
441 flag_value: summary.flag_value,
442 value_type: format!("{:?}", summary.value_type),
443 }
444 }
445 }
446
447 /// implement flag value and info summary cxx return type
448 impl ffi::FlagValueAndInfoSummaryCXX {
new(summary: FlagValueAndInfoSummary) -> Self449 pub(crate) fn new(summary: FlagValueAndInfoSummary) -> Self {
450 Self {
451 package_name: summary.package_name,
452 flag_name: summary.flag_name,
453 flag_value: summary.flag_value,
454 value_type: format!("{:?}", summary.value_type),
455 is_readwrite: summary.is_readwrite,
456 has_server_override: summary.has_server_override,
457 has_local_override: summary.has_local_override,
458 }
459 }
460 }
461
462 /// implement list flag cxx interlop
list_flags_cxx( package_map: &str, flag_map: &str, flag_val: &str, ) -> ffi::ListFlagValueResultCXX463 pub fn list_flags_cxx(
464 package_map: &str,
465 flag_map: &str,
466 flag_val: &str,
467 ) -> ffi::ListFlagValueResultCXX {
468 match list_flags(package_map, flag_map, flag_val) {
469 Ok(summary) => ffi::ListFlagValueResultCXX {
470 query_success: true,
471 error_message: String::new(),
472 flags: summary.into_iter().map(ffi::FlagValueSummaryCXX::new).collect(),
473 },
474 Err(errmsg) => ffi::ListFlagValueResultCXX {
475 query_success: false,
476 error_message: format!("{:?}", errmsg),
477 flags: Vec::new(),
478 },
479 }
480 }
481
482 /// implement list flag with info cxx interlop
list_flags_with_info_cxx( package_map: &str, flag_map: &str, flag_val: &str, flag_info: &str, ) -> ffi::ListFlagValueAndInfoResultCXX483 pub fn list_flags_with_info_cxx(
484 package_map: &str,
485 flag_map: &str,
486 flag_val: &str,
487 flag_info: &str,
488 ) -> ffi::ListFlagValueAndInfoResultCXX {
489 match list_flags_with_info(package_map, flag_map, flag_val, flag_info) {
490 Ok(summary) => ffi::ListFlagValueAndInfoResultCXX {
491 query_success: true,
492 error_message: String::new(),
493 flags: summary.into_iter().map(ffi::FlagValueAndInfoSummaryCXX::new).collect(),
494 },
495 Err(errmsg) => ffi::ListFlagValueAndInfoResultCXX {
496 query_success: false,
497 error_message: format!("{:?}", errmsg),
498 flags: Vec::new(),
499 },
500 }
501 }
502
503 #[cfg(test)]
504 mod tests {
505 use super::*;
506 use crate::test_utils::{
507 create_test_flag_info_list, create_test_flag_table, create_test_flag_value_list,
508 create_test_package_table, write_bytes_to_temp_file,
509 };
510
511 #[test]
512 // this test point locks down the flag list api
test_list_flag()513 fn test_list_flag() {
514 let package_table =
515 write_bytes_to_temp_file(&create_test_package_table().into_bytes()).unwrap();
516 let flag_table = write_bytes_to_temp_file(&create_test_flag_table().into_bytes()).unwrap();
517 let flag_value_list =
518 write_bytes_to_temp_file(&create_test_flag_value_list().into_bytes()).unwrap();
519
520 let package_table_path = package_table.path().display().to_string();
521 let flag_table_path = flag_table.path().display().to_string();
522 let flag_value_list_path = flag_value_list.path().display().to_string();
523
524 let flags =
525 list_flags(&package_table_path, &flag_table_path, &flag_value_list_path).unwrap();
526 let expected = [
527 FlagValueSummary {
528 package_name: String::from("com.android.aconfig.storage.test_1"),
529 flag_name: String::from("disabled_rw"),
530 value_type: StoredFlagType::ReadWriteBoolean,
531 flag_value: String::from("false"),
532 },
533 FlagValueSummary {
534 package_name: String::from("com.android.aconfig.storage.test_1"),
535 flag_name: String::from("enabled_ro"),
536 value_type: StoredFlagType::ReadOnlyBoolean,
537 flag_value: String::from("true"),
538 },
539 FlagValueSummary {
540 package_name: String::from("com.android.aconfig.storage.test_1"),
541 flag_name: String::from("enabled_rw"),
542 value_type: StoredFlagType::ReadWriteBoolean,
543 flag_value: String::from("true"),
544 },
545 FlagValueSummary {
546 package_name: String::from("com.android.aconfig.storage.test_2"),
547 flag_name: String::from("disabled_rw"),
548 value_type: StoredFlagType::ReadWriteBoolean,
549 flag_value: String::from("false"),
550 },
551 FlagValueSummary {
552 package_name: String::from("com.android.aconfig.storage.test_2"),
553 flag_name: String::from("enabled_fixed_ro"),
554 value_type: StoredFlagType::FixedReadOnlyBoolean,
555 flag_value: String::from("true"),
556 },
557 FlagValueSummary {
558 package_name: String::from("com.android.aconfig.storage.test_2"),
559 flag_name: String::from("enabled_ro"),
560 value_type: StoredFlagType::ReadOnlyBoolean,
561 flag_value: String::from("true"),
562 },
563 FlagValueSummary {
564 package_name: String::from("com.android.aconfig.storage.test_4"),
565 flag_name: String::from("enabled_fixed_ro"),
566 value_type: StoredFlagType::FixedReadOnlyBoolean,
567 flag_value: String::from("true"),
568 },
569 FlagValueSummary {
570 package_name: String::from("com.android.aconfig.storage.test_4"),
571 flag_name: String::from("enabled_rw"),
572 value_type: StoredFlagType::ReadWriteBoolean,
573 flag_value: String::from("true"),
574 },
575 ];
576 assert_eq!(flags, expected);
577 }
578
579 #[test]
580 // this test point locks down the flag list with info api
test_list_flag_with_info()581 fn test_list_flag_with_info() {
582 let package_table =
583 write_bytes_to_temp_file(&create_test_package_table().into_bytes()).unwrap();
584 let flag_table = write_bytes_to_temp_file(&create_test_flag_table().into_bytes()).unwrap();
585 let flag_value_list =
586 write_bytes_to_temp_file(&create_test_flag_value_list().into_bytes()).unwrap();
587 let flag_info_list =
588 write_bytes_to_temp_file(&create_test_flag_info_list().into_bytes()).unwrap();
589
590 let package_table_path = package_table.path().display().to_string();
591 let flag_table_path = flag_table.path().display().to_string();
592 let flag_value_list_path = flag_value_list.path().display().to_string();
593 let flag_info_list_path = flag_info_list.path().display().to_string();
594
595 let flags = list_flags_with_info(
596 &package_table_path,
597 &flag_table_path,
598 &flag_value_list_path,
599 &flag_info_list_path,
600 )
601 .unwrap();
602 let expected = [
603 FlagValueAndInfoSummary {
604 package_name: String::from("com.android.aconfig.storage.test_1"),
605 flag_name: String::from("disabled_rw"),
606 value_type: StoredFlagType::ReadWriteBoolean,
607 flag_value: String::from("false"),
608 is_readwrite: true,
609 has_server_override: false,
610 has_local_override: false,
611 },
612 FlagValueAndInfoSummary {
613 package_name: String::from("com.android.aconfig.storage.test_1"),
614 flag_name: String::from("enabled_ro"),
615 value_type: StoredFlagType::ReadOnlyBoolean,
616 flag_value: String::from("true"),
617 is_readwrite: false,
618 has_server_override: false,
619 has_local_override: false,
620 },
621 FlagValueAndInfoSummary {
622 package_name: String::from("com.android.aconfig.storage.test_1"),
623 flag_name: String::from("enabled_rw"),
624 value_type: StoredFlagType::ReadWriteBoolean,
625 flag_value: String::from("true"),
626 is_readwrite: true,
627 has_server_override: false,
628 has_local_override: false,
629 },
630 FlagValueAndInfoSummary {
631 package_name: String::from("com.android.aconfig.storage.test_2"),
632 flag_name: String::from("disabled_rw"),
633 value_type: StoredFlagType::ReadWriteBoolean,
634 flag_value: String::from("false"),
635 is_readwrite: true,
636 has_server_override: false,
637 has_local_override: false,
638 },
639 FlagValueAndInfoSummary {
640 package_name: String::from("com.android.aconfig.storage.test_2"),
641 flag_name: String::from("enabled_fixed_ro"),
642 value_type: StoredFlagType::FixedReadOnlyBoolean,
643 flag_value: String::from("true"),
644 is_readwrite: false,
645 has_server_override: false,
646 has_local_override: false,
647 },
648 FlagValueAndInfoSummary {
649 package_name: String::from("com.android.aconfig.storage.test_2"),
650 flag_name: String::from("enabled_ro"),
651 value_type: StoredFlagType::ReadOnlyBoolean,
652 flag_value: String::from("true"),
653 is_readwrite: false,
654 has_server_override: false,
655 has_local_override: false,
656 },
657 FlagValueAndInfoSummary {
658 package_name: String::from("com.android.aconfig.storage.test_4"),
659 flag_name: String::from("enabled_fixed_ro"),
660 value_type: StoredFlagType::FixedReadOnlyBoolean,
661 flag_value: String::from("true"),
662 is_readwrite: false,
663 has_server_override: false,
664 has_local_override: false,
665 },
666 FlagValueAndInfoSummary {
667 package_name: String::from("com.android.aconfig.storage.test_4"),
668 flag_name: String::from("enabled_rw"),
669 value_type: StoredFlagType::ReadWriteBoolean,
670 flag_value: String::from("true"),
671 is_readwrite: true,
672 has_server_override: false,
673 has_local_override: false,
674 },
675 ];
676 assert_eq!(flags, expected);
677 }
678 }
679