1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use super::*;
16 use mockall::predicate::*;
17 use mockall::Sequence;
18 use std::os::unix::process::ExitStatusExt;
19 use std::sync::LockResult;
20 use std::sync::Mutex;
21 use std::sync::MutexGuard;
22 
23 use crate::zram::MockSysfsZramApi;
24 use crate::zram::ZRAM_API_MTX;
25 
26 const PROC_SWAP_HEADER: &str = "Filename                                Type            Size            Used            Priority\n";
27 const DEFAULT_PAGE_SIZE: u64 = 4096;
28 const DEFAULT_PAGE_COUNT: u64 = 998875;
29 const DEFAULT_ZRAM_SIZE: u64 = 1000000;
30 
success_command_output() -> std::process::Output31 fn success_command_output() -> std::process::Output {
32     std::process::Output {
33         status: std::process::ExitStatus::from_raw(0),
34         stderr: "".to_owned().into_bytes(),
35         stdout: "".to_owned().into_bytes(),
36     }
37 }
38 
failure_command_output() -> std::process::Output39 fn failure_command_output() -> std::process::Output {
40     std::process::Output {
41         status: std::process::ExitStatus::from_raw(1),
42         stderr: "".to_owned().into_bytes(),
43         stdout: "".to_owned().into_bytes(),
44     }
45 }
46 
47 /// Mutex to synchronize tests using [MockSetupApi].
48 ///
49 /// mockall for static functions requires synchronization.
50 ///
51 /// https://docs.rs/mockall/latest/mockall/#static-methods
52 pub static SETUP_API_MTX: Mutex<()> = Mutex::new(());
53 
54 struct MockContext<'a> {
55     write_disksize: crate::zram::__mock_MockSysfsZramApi_SysfsZramApi::__write_disksize::Context,
56     read_swap_areas: crate::zram::setup::__mock_MockSetupApi_SetupApi::__read_swap_areas::Context,
57     mkswap: crate::zram::setup::__mock_MockSetupApi_SetupApi::__mkswap::Context,
58     swapon: crate::zram::setup::__mock_MockSetupApi_SetupApi::__swapon::Context,
59     // Lock will be released after mock contexts are dropped.
60     _setup_lock: LockResult<MutexGuard<'a, ()>>,
61     _zram_lock: LockResult<MutexGuard<'a, ()>>,
62 }
63 
64 impl<'a> MockContext<'a> {
new() -> Self65     fn new() -> Self {
66         let _zram_lock = ZRAM_API_MTX.lock();
67         let _setup_lock = SETUP_API_MTX.lock();
68         Self {
69             write_disksize: MockSysfsZramApi::write_disksize_context(),
70             read_swap_areas: MockSetupApi::read_swap_areas_context(),
71             mkswap: MockSetupApi::mkswap_context(),
72             swapon: MockSetupApi::swapon_context(),
73             _setup_lock,
74             _zram_lock,
75         }
76     }
77 }
78 
79 #[test]
is_zram_swap_activated_zram_off()80 fn is_zram_swap_activated_zram_off() {
81     let mock = MockContext::new();
82     mock.read_swap_areas.expect().returning(|| Ok(PROC_SWAP_HEADER.to_string()));
83 
84     assert!(!is_zram_swap_activated::<MockSetupApi>().unwrap());
85 }
86 
87 #[test]
is_zram_swap_activated_zram_on()88 fn is_zram_swap_activated_zram_on() {
89     let mock = MockContext::new();
90     let zram_area = "/dev/block/zram0                        partition       7990996         87040           -2\n";
91     mock.read_swap_areas.expect().returning(|| Ok(PROC_SWAP_HEADER.to_owned() + zram_area));
92 
93     assert!(is_zram_swap_activated::<MockSetupApi>().unwrap());
94 }
95 
96 #[test]
parse_zram_spec_invalid()97 fn parse_zram_spec_invalid() {
98     assert!(parse_size_spec_with_page_info("", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err());
99     assert!(
100         parse_size_spec_with_page_info("not_int%", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err()
101     );
102     assert!(
103         parse_size_spec_with_page_info("not_int", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err()
104     );
105 }
106 
107 #[test]
parse_zram_spec_percentage_out_of_range()108 fn parse_zram_spec_percentage_out_of_range() {
109     assert!(parse_size_spec_with_page_info("0%", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err());
110     assert!(parse_size_spec_with_page_info("501%", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err());
111 }
112 
113 #[test]
parse_zram_spec_percentage()114 fn parse_zram_spec_percentage() {
115     assert_eq!(parse_size_spec_with_page_info("33%", 4096, 5).unwrap(), 4096);
116     assert_eq!(parse_size_spec_with_page_info("50%", 4096, 5).unwrap(), 8192);
117     assert_eq!(parse_size_spec_with_page_info("100%", 4096, 5).unwrap(), 20480);
118     assert_eq!(parse_size_spec_with_page_info("200%", 4096, 5).unwrap(), 40960);
119     assert_eq!(parse_size_spec_with_page_info("100%", 4096, 3995500).unwrap(), 16365568000);
120 }
121 
122 #[test]
parse_zram_spec_bytes()123 fn parse_zram_spec_bytes() {
124     assert_eq!(
125         parse_size_spec_with_page_info("1234567", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).unwrap(),
126         1234567
127     );
128 }
129 
130 #[test]
activate_success()131 fn activate_success() {
132     let mock = MockContext::new();
133     let zram_size = 123456;
134     let mut seq = Sequence::new();
135     mock.write_disksize
136         .expect()
137         .with(eq("123456"))
138         .times(1)
139         .returning(|_| Ok(()))
140         .in_sequence(&mut seq);
141     mock.mkswap
142         .expect()
143         .with(eq(ZRAM_DEVICE_PATH))
144         .times(1)
145         .returning(|_| Ok(success_command_output()))
146         .in_sequence(&mut seq);
147     mock.swapon
148         .expect()
149         .with(eq(std::ffi::CString::new(ZRAM_DEVICE_PATH).unwrap()))
150         .times(1)
151         .returning(|_| Ok(()))
152         .in_sequence(&mut seq);
153 
154     assert!(activate_zram::<MockSysfsZramApi, MockSetupApi>(zram_size).is_ok());
155 }
156 
157 #[test]
activate_failed_update_size()158 fn activate_failed_update_size() {
159     let mock = MockContext::new();
160     mock.write_disksize.expect().returning(|_| Err(std::io::Error::other("error")));
161 
162     assert!(matches!(
163         activate_zram::<MockSysfsZramApi, MockSetupApi>(DEFAULT_ZRAM_SIZE),
164         Err(ZramActivationError::UpdateZramDiskSize(_))
165     ));
166 }
167 
168 #[test]
activate_failed_mkswap()169 fn activate_failed_mkswap() {
170     let mock = MockContext::new();
171     mock.write_disksize.expect().returning(|_| Ok(()));
172     mock.mkswap.expect().returning(|_| Ok(failure_command_output()));
173 
174     assert!(matches!(
175         activate_zram::<MockSysfsZramApi, MockSetupApi>(DEFAULT_ZRAM_SIZE),
176         Err(ZramActivationError::MkSwap(_))
177     ));
178 }
179 
180 #[test]
activate_failed_swapon()181 fn activate_failed_swapon() {
182     let mock = MockContext::new();
183     mock.write_disksize.expect().returning(|_| Ok(()));
184     mock.mkswap.expect().returning(|_| Ok(success_command_output()));
185     mock.swapon.expect().returning(|_| Err(std::io::Error::other("error")));
186 
187     assert!(matches!(
188         activate_zram::<MockSysfsZramApi, MockSetupApi>(DEFAULT_ZRAM_SIZE),
189         Err(ZramActivationError::SwapOn(_))
190     ));
191 }
192