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 #[cfg(test)]
18 pub use test_utils::*;
19 
20 #[cfg(test)]
21 pub mod test_utils {
22     use crate::commands::Input;
23     use aconfig_protos::ProtoParsedFlags;
24     use itertools;
25 
26     pub const TEST_PACKAGE: &str = "com.android.aconfig.test";
27 
28     pub const TEST_FLAGS_TEXTPROTO: &str = r#"
29 parsed_flag {
30   package: "com.android.aconfig.test"
31   name: "disabled_ro"
32   namespace: "aconfig_test"
33   description: "This flag is DISABLED + READ_ONLY"
34   bug: "123"
35   state: DISABLED
36   permission: READ_ONLY
37   trace {
38     source: "tests/test.aconfig"
39     state: DISABLED
40     permission: READ_WRITE
41   }
42   trace {
43     source: "tests/first.values"
44     state: DISABLED
45     permission: READ_ONLY
46   }
47   is_fixed_read_only: false
48   is_exported: false
49   container: "system"
50   metadata {
51     purpose: PURPOSE_UNSPECIFIED
52   }
53 }
54 parsed_flag {
55   package: "com.android.aconfig.test"
56   name: "disabled_rw"
57   namespace: "aconfig_test"
58   description: "This flag is DISABLED + READ_WRITE"
59   bug: "456"
60   state: DISABLED
61   permission: READ_WRITE
62   trace {
63     source: "tests/test.aconfig"
64     state: DISABLED
65     permission: READ_WRITE
66   }
67   is_fixed_read_only: false
68   is_exported: false
69   container: "system"
70   metadata {
71     purpose: PURPOSE_UNSPECIFIED
72   }
73 }
74 parsed_flag {
75   package: "com.android.aconfig.test"
76   name: "disabled_rw_exported"
77   namespace: "aconfig_test"
78   description: "This flag is DISABLED + READ_WRITE and exported"
79   bug: "111"
80   state: DISABLED
81   permission: READ_WRITE
82   trace {
83     source: "tests/test.aconfig"
84     state: DISABLED
85     permission: READ_WRITE
86   }
87   trace {
88     source: "tests/first.values"
89     state: DISABLED
90     permission: READ_WRITE
91   }
92   is_fixed_read_only: false
93   is_exported: true
94   container: "system"
95   metadata {
96     purpose: PURPOSE_UNSPECIFIED
97   }
98 }
99 parsed_flag {
100   package: "com.android.aconfig.test"
101   name: "disabled_rw_in_other_namespace"
102   namespace: "other_namespace"
103   description: "This flag is DISABLED + READ_WRITE, and is defined in another namespace"
104   bug: "999"
105   state: DISABLED
106   permission: READ_WRITE
107   trace {
108     source: "tests/test.aconfig"
109     state: DISABLED
110     permission: READ_WRITE
111   }
112   trace {
113     source: "tests/first.values"
114     state: DISABLED
115     permission: READ_WRITE
116   }
117   is_fixed_read_only: false
118   is_exported: false
119   container: "system"
120   metadata {
121     purpose: PURPOSE_UNSPECIFIED
122   }
123 }
124 parsed_flag {
125   package: "com.android.aconfig.test"
126   name: "enabled_fixed_ro"
127   namespace: "aconfig_test"
128   description: "This flag is fixed READ_ONLY + ENABLED"
129   bug: ""
130   state: ENABLED
131   permission: READ_ONLY
132   trace {
133     source: "tests/test.aconfig"
134     state: DISABLED
135     permission: READ_ONLY
136   }
137   trace {
138     source: "tests/first.values"
139     state: ENABLED
140     permission: READ_ONLY
141   }
142   is_fixed_read_only: true
143   is_exported: false
144   container: "system"
145   metadata {
146     purpose: PURPOSE_UNSPECIFIED
147   }
148 }
149 parsed_flag {
150   package: "com.android.aconfig.test"
151   name: "enabled_fixed_ro_exported"
152   namespace: "aconfig_test"
153   description: "This flag is fixed ENABLED + READ_ONLY and exported"
154   bug: "111"
155   state: ENABLED
156   permission: READ_ONLY
157   trace {
158     source: "tests/test.aconfig"
159     state: DISABLED
160     permission: READ_ONLY
161   }
162   trace {
163     source: "tests/first.values"
164     state: ENABLED
165     permission: READ_ONLY
166   }
167   is_fixed_read_only: true
168   is_exported: true
169   container: "system"
170   metadata {
171     purpose: PURPOSE_UNSPECIFIED
172   }
173 }
174 parsed_flag {
175   package: "com.android.aconfig.test"
176   name: "enabled_ro"
177   namespace: "aconfig_test"
178   description: "This flag is ENABLED + READ_ONLY"
179   bug: "abc"
180   state: ENABLED
181   permission: READ_ONLY
182   trace {
183     source: "tests/test.aconfig"
184     state: DISABLED
185     permission: READ_WRITE
186   }
187   trace {
188     source: "tests/first.values"
189     state: DISABLED
190     permission: READ_WRITE
191   }
192   trace {
193     source: "tests/second.values"
194     state: ENABLED
195     permission: READ_ONLY
196   }
197   is_fixed_read_only: false
198   is_exported: false
199   container: "system"
200   metadata {
201     purpose: PURPOSE_BUGFIX
202   }
203 }
204 parsed_flag {
205   package: "com.android.aconfig.test"
206   name: "enabled_ro_exported"
207   namespace: "aconfig_test"
208   description: "This flag is ENABLED + READ_ONLY and exported"
209   bug: "111"
210   state: ENABLED
211   permission: READ_ONLY
212   trace {
213     source: "tests/test.aconfig"
214     state: DISABLED
215     permission: READ_WRITE
216   }
217   trace {
218     source: "tests/first.values"
219     state: ENABLED
220     permission: READ_ONLY
221   }
222   is_fixed_read_only: false
223   is_exported: true
224   container: "system"
225   metadata {
226     purpose: PURPOSE_UNSPECIFIED
227   }
228 }
229 parsed_flag {
230   package: "com.android.aconfig.test"
231   name: "enabled_rw"
232   namespace: "aconfig_test"
233   description: "This flag is ENABLED + READ_WRITE"
234   bug: ""
235   state: ENABLED
236   permission: READ_WRITE
237   trace {
238     source: "tests/test.aconfig"
239     state: DISABLED
240     permission: READ_WRITE
241   }
242   trace {
243     source: "tests/first.values"
244     state: ENABLED
245     permission: READ_WRITE
246   }
247   is_fixed_read_only: false
248   is_exported: false
249   container: "system"
250   metadata {
251     purpose: PURPOSE_UNSPECIFIED
252   }
253 }
254 "#;
255 
parse_read_only_test_flags() -> ProtoParsedFlags256     pub fn parse_read_only_test_flags() -> ProtoParsedFlags {
257         let bytes = crate::commands::parse_flags(
258             "com.android.aconfig.test",
259             Some("system"),
260             vec![Input {
261                 source: "tests/read_only_test.aconfig".to_string(),
262                 reader: Box::new(include_bytes!("../tests/read_only_test.aconfig").as_slice()),
263             }],
264             vec![Input {
265                 source: "tests/read_only_test.values".to_string(),
266                 reader: Box::new(include_bytes!("../tests/read_only_test.values").as_slice()),
267             }],
268             crate::commands::DEFAULT_FLAG_PERMISSION,
269         )
270         .unwrap();
271         aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
272     }
273 
parse_test_flags() -> ProtoParsedFlags274     pub fn parse_test_flags() -> ProtoParsedFlags {
275         let bytes = crate::commands::parse_flags(
276             "com.android.aconfig.test",
277             Some("system"),
278             vec![Input {
279                 source: "tests/test.aconfig".to_string(),
280                 reader: Box::new(include_bytes!("../tests/test.aconfig").as_slice()),
281             }],
282             vec![
283                 Input {
284                     source: "tests/first.values".to_string(),
285                     reader: Box::new(include_bytes!("../tests/first.values").as_slice()),
286                 },
287                 Input {
288                     source: "tests/second.values".to_string(),
289                     reader: Box::new(include_bytes!("../tests/second.values").as_slice()),
290                 },
291             ],
292             crate::commands::DEFAULT_FLAG_PERMISSION,
293         )
294         .unwrap();
295         aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
296     }
297 
first_significant_code_diff(a: &str, b: &str) -> Option<String>298     pub fn first_significant_code_diff(a: &str, b: &str) -> Option<String> {
299         let a = a.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty());
300         let b = b.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty());
301         match itertools::diff_with(a, b, |left, right| left == right) {
302             Some(itertools::Diff::FirstMismatch(_, mut left, mut right)) => {
303                 Some(format!("'{}' vs '{}'", left.next().unwrap(), right.next().unwrap()))
304             }
305             Some(itertools::Diff::Shorter(_, mut left)) => {
306                 Some(format!("LHS trailing data: '{}'", left.next().unwrap()))
307             }
308             Some(itertools::Diff::Longer(_, mut right)) => {
309                 Some(format!("RHS trailing data: '{}'", right.next().unwrap()))
310             }
311             None => None,
312         }
313     }
314 
315     #[test]
test_first_significant_code_diff()316     fn test_first_significant_code_diff() {
317         assert!(first_significant_code_diff("", "").is_none());
318         assert!(first_significant_code_diff("   a", "\n\na\n").is_none());
319         let a = r#"
320         public class A {
321             private static final String FOO = "FOO";
322             public static void main(String[] args) {
323                 System.out.println("FOO=" + FOO);
324             }
325         }
326         "#;
327         let b = r#"
328         public class A {
329             private static final String FOO = "BAR";
330             public static void main(String[] args) {
331                 System.out.println("foo=" + FOO);
332             }
333         }
334         "#;
335         assert_eq!(Some(r#"'private static final String FOO = "FOO";' vs 'private static final String FOO = "BAR";'"#.to_string()), first_significant_code_diff(a, b));
336         assert_eq!(
337             Some("LHS trailing data: 'b'".to_string()),
338             first_significant_code_diff("a\nb", "a")
339         );
340         assert_eq!(
341             Some("RHS trailing data: 'b'".to_string()),
342             first_significant_code_diff("a", "a\nb")
343         );
344     }
345 }
346