gear_utils/
lib.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Useful utilities needed for testing and other stuff.
20
21use gear_core::{memory::PageBuf, pages::GearPage};
22pub use nonempty::NonEmpty;
23use parity_scale_codec::{Decode, Encode};
24use path_clean::PathClean;
25use serde::{Deserialize, Serialize};
26use std::{
27    env, fs,
28    path::Path,
29    time::{Duration, SystemTime, UNIX_EPOCH},
30};
31
32pub mod codegen;
33
34/// Trait describes a collection which can get a value by it's index.
35/// The index can be in any range, even [length(implementor), ..).
36///
37/// The key feature of the trait is that the implementor should guarantee
38/// that with under provided index there's always some value. The best way
39/// to do that is to implement a trait for a guaranteed not empty type.
40pub trait RingGet<V> {
41    /// Returns with a guarantee a value under `index`.
42    fn ring_get(&self, index: usize) -> &V;
43}
44
45impl<V> RingGet<V> for NonEmpty<V> {
46    fn ring_get(&self, index: usize) -> &V {
47        // Guaranteed to have value, because index is in the range of [0; self.len()).
48        self.get(index % self.len()).expect("guaranteed to be some")
49    }
50}
51
52/// Returns time elapsed since [`UNIX_EPOCH`] in milliseconds.
53pub fn now_millis() -> u64 {
54    now_duration().as_millis() as u64
55}
56
57/// Returns time elapsed since [`UNIX_EPOCH`] in microseconds.
58pub fn now_micros() -> u128 {
59    now_duration().as_micros()
60}
61
62/// Returns [`Duration`] from [`UNIX_EPOCH`] until now.
63pub fn now_duration() -> Duration {
64    SystemTime::now()
65        .duration_since(UNIX_EPOCH)
66        .expect("Internal error: current time before UNIX Epoch")
67}
68
69/// Initialize a simple logger from env.
70///
71/// Does show:
72/// - level
73/// - timestamp
74/// - module
75///
76/// Does not show
77/// - module path
78pub fn init_default_logger() {
79    let _ = tracing_subscriber::fmt::try_init();
80}
81
82/// Stores one memory page dump as the hex string.
83#[derive(Serialize, Deserialize)]
84pub struct MemoryPageDump {
85    page: u32,
86    data: Option<String>,
87}
88
89impl MemoryPageDump {
90    pub fn new(page_number: GearPage, page_data: PageBuf) -> MemoryPageDump {
91        let mut data_vec = vec![];
92        page_data.encode_to(&mut data_vec);
93        let data = data_vec
94            .iter()
95            .any(|&byte| byte != 0)
96            .then(|| hex::encode(data_vec));
97
98        MemoryPageDump {
99            page: page_number.into(),
100            data,
101        }
102    }
103
104    pub fn into_gear_page(self) -> (GearPage, PageBuf) {
105        let page_buf = if let Some(page_hex) = self.data {
106            let data = hex::decode(page_hex).expect("Unexpected memory page data encoding");
107            PageBuf::decode(&mut &*data).expect("Invalid PageBuf data found")
108        } else {
109            PageBuf::new_zeroed()
110        };
111        (
112            GearPage::try_from(self.page)
113                .unwrap_or_else(|_| panic!("Couldn't decode GearPage from u32: {}", self.page)),
114            page_buf,
115        )
116    }
117}
118
119/// Stores all program's page dumps and it's balance.
120#[derive(Serialize, Deserialize, Default)]
121pub struct ProgramMemoryDump {
122    pub balance: u128,
123    pub reserved_balance: u128,
124    pub pages: Vec<MemoryPageDump>,
125}
126
127impl ProgramMemoryDump {
128    pub fn save_to_file(&self, path: impl AsRef<Path>) {
129        let path = env::current_dir()
130            .expect("Unable to get root directory of the project")
131            .join(path)
132            .clean();
133
134        if let Some(parent) = path.parent() {
135            fs::create_dir_all(parent)
136                .unwrap_or_else(|_| panic!("Couldn't create folder {}", parent.display()));
137        }
138
139        let data =
140            serde_json::to_string(&self).expect("Failed to serialize ProgramMemoryDump to JSON");
141
142        fs::write(&path, data).unwrap_or_else(|_| panic!("Failed to write file {path:?}"));
143    }
144
145    pub fn load_from_file(path: impl AsRef<Path>) -> ProgramMemoryDump {
146        let path = env::current_dir()
147            .expect("Unable to get root directory of the project")
148            .join(path)
149            .clean();
150
151        let json =
152            fs::read_to_string(&path).unwrap_or_else(|_| panic!("Failed to read file {path:?}"));
153
154        serde_json::from_str(&json)
155            .unwrap_or_else(|_| panic!("Failed to deserialize {path:?} as ProgramMemoryDump"))
156    }
157}