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