• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 16254190946

13 Jul 2025 10:39PM UTC coverage: 88.737%. First build
16254190946

Pull #1061

github

web-flow
Merge ecb95f8b6 into b8910c811
Pull Request #1061: feat(operators): skip empty tiles and merge masks in onnx; remove trace/debug in release mode

155 of 233 new or added lines in 35 files covered. (66.52%)

111351 of 125484 relevant lines covered (88.74%)

80359.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.52
/expression/src/compiled.rs
1
use crate::{
2
    ExpressionAst, ExpressionDependencies,
3
    error::{self, CompilationFailed, Compiler, ExpressionExecutionError},
4
};
5
use libloading::{Library, Symbol, library_filename};
6
use snafu::{ResultExt, ensure};
7
use std::{
8
    borrow::Cow,
9
    fs::File,
10
    io::Write,
11
    mem::ManuallyDrop,
12
    path::{Path, PathBuf},
13
    process::Command,
14
};
15
use tempfile::TempDir;
16

17
pub type Result<T, E = ExpressionExecutionError> = std::result::Result<T, E>;
18

19
/// Compiles and links an expression as a program and offers means to call it
20
pub struct LinkedExpression {
21
    library_folder: ManuallyDrop<TempDir>,
22
    library: ManuallyDrop<Library>,
23
    function_name: String,
24
}
25

26
impl LinkedExpression {
27
    pub fn new(
24✔
28
        function_name: &str,
24✔
29
        code: &str,
24✔
30
        dependencies: &ExpressionDependencies,
24✔
31
    ) -> Result<Self> {
24✔
32
        let library_folder =
24✔
33
            tempfile::tempdir().context(error::CannotGenerateSourceCodeDirectory)?;
24✔
34

35
        // format code in debug mode
36
        let mut code = Cow::from(code);
24✔
37
        if std::cfg!(debug_assertions) {
24✔
38
            code = syn::parse_file(&code)
24✔
39
                .map_or_else(
24✔
40
                    |e| {
×
41
                        // fallback to unformatted code
NEW
42
                        tracing::error!("Cannot parse expression: {e}");
×
43
                        code.to_string()
×
44
                    },
×
45
                    |file| prettyplease::unparse(&file),
24✔
46
                )
47
                .into();
24✔
48
        }
×
49

50
        let input_filename = create_source_code_file(library_folder.path(), &code)
24✔
51
            .context(error::CannotGenerateSourceCodeFile)?;
24✔
52

53
        let library_filename = compile_file(library_folder.path(), &input_filename, dependencies)?;
24✔
54

55
        let library = unsafe { Library::new(library_filename) }.context(error::LinkExpression)?;
24✔
56

57
        Ok(Self {
24✔
58
            library_folder: ManuallyDrop::new(library_folder),
24✔
59
            library: ManuallyDrop::new(library),
24✔
60
            function_name: function_name.to_string(),
24✔
61
        })
24✔
62
    }
24✔
63

64
    pub fn from_ast(ast: &ExpressionAst, dependencies: &ExpressionDependencies) -> Result<Self> {
6✔
65
        let code = if std::cfg!(debug_assertions) {
6✔
66
            ast.pretty_code()
6✔
67
        } else {
68
            ast.code()
×
69
        };
70
        Self::new(ast.name(), &code, dependencies)
6✔
71
    }
6✔
72

73
    /// Returns a function with 1 input parameters
74
    ///
75
    /// # Safety
76
    ///
77
    /// The caller must ensure that the function is called with the correct type of input parameter
78
    ///
79
    #[allow(clippy::type_complexity)]
80
    pub unsafe fn function_1<A>(&self) -> Result<Symbol<fn(A) -> Option<f64>>> {
29✔
81
        unsafe {
82
            self.library
29✔
83
                .get(self.function_name.as_bytes())
29✔
84
                .context(error::LinkedFunctionNotFound {
29✔
85
                    name: self.function_name.clone(),
29✔
86
                })
29✔
87
        }
88
    }
29✔
89
    /// Returns a function with 3 input parameters
90
    ///
91
    /// # Safety
92
    ///
93
    /// The caller must ensure that the function is called with the correct type of input parameter
94
    ///
95
    #[allow(clippy::type_complexity)]
96
    pub unsafe fn function_2<A, B>(&self) -> Result<Symbol<fn(A, B) -> Option<f64>>> {
27✔
97
        unsafe {
98
            self.library
27✔
99
                .get(self.function_name.as_bytes())
27✔
100
                .context(error::LinkedFunctionNotFound {
27✔
101
                    name: self.function_name.clone(),
27✔
102
                })
27✔
103
        }
104
    }
27✔
105

106
    /// Returns an n-ary function
107
    ///
108
    /// # Safety
109
    ///
110
    /// The caller must ensure that the function is called with the correct type of input and output parameters
111
    ///
112
    #[allow(clippy::type_complexity)]
113
    pub unsafe fn function_nary<F>(&self) -> Result<Symbol<F>> {
10✔
114
        unsafe {
115
            self.library
10✔
116
                .get(self.function_name.as_bytes())
10✔
117
                .context(error::LinkedFunctionNotFound {
10✔
118
                    name: self.function_name.clone(),
10✔
119
                })
10✔
120
        }
121
    }
10✔
122
}
123

124
impl Drop for LinkedExpression {
125
    fn drop(&mut self) {
24✔
126
        // first unlink program…
127
        unsafe { ManuallyDrop::drop(&mut self.library) };
24✔
128

129
        // …then delete files
130
        unsafe { ManuallyDrop::drop(&mut self.library_folder) };
24✔
131
    }
24✔
132
}
133

134
fn create_source_code_file(
24✔
135
    library_folder: &Path,
24✔
136
    source_code: &str,
24✔
137
) -> Result<PathBuf, std::io::Error> {
24✔
138
    let input_filename = library_folder.join("expression.rs");
24✔
139

140
    let mut file = File::create(&input_filename)?;
24✔
141
    file.write_all(source_code.as_bytes())?;
24✔
142

143
    Ok(input_filename)
24✔
144
}
24✔
145

146
fn compile_file(
24✔
147
    library_folder: &Path,
24✔
148
    input_filename: &Path,
24✔
149
    dependencies: &ExpressionDependencies,
24✔
150
) -> Result<PathBuf> {
24✔
151
    let output_filename = library_folder.join(library_filename("expression"));
24✔
152

153
    let mut command = Command::new("rustc");
24✔
154
    command
24✔
155
        .args(["--edition", "2024"])
24✔
156
        .args(["--crate-type", "cdylib"])
24✔
157
        .args(["-C", "opt-level=3"])
24✔
158
        .arg("-L")
24✔
159
        .arg(dependencies.linker_path());
24✔
160

161
    if !std::cfg!(debug_assertions) {
24✔
162
        command.args(["-A", "warnings"]);
×
163
    }
24✔
164

165
    let output = command
24✔
166
        .arg("-o")
24✔
167
        .arg(&output_filename)
24✔
168
        .arg(input_filename)
24✔
169
        .output()
24✔
170
        .context(Compiler)?;
24✔
171

172
    ensure!(
24✔
173
        output.status.success(),
24✔
174
        CompilationFailed {
×
175
            stderr: String::from_utf8_lossy(&output.stderr),
×
176
            stdout: String::from_utf8_lossy(&output.stdout),
×
177
        }
×
178
    );
179

180
    Ok(output_filename)
24✔
181
}
24✔
182

183
#[cfg(test)]
184
mod tests {
185
    use crate::{DataType, ExpressionParser, Parameter};
186

187
    use super::*;
188
    use geoengine_expression_deps::{MultiPoint, MultiPolygon};
189
    use quote::quote;
190

191
    #[test]
192
    #[allow(clippy::float_cmp)]
193
    fn it_compiles_hello_world() {
1✔
194
        let dependencies = ExpressionDependencies::new().unwrap();
1✔
195

196
        let source_code = quote! {
1✔
197
            extern crate geo;
198
            extern crate geoengine_expression_deps;
199

200
            use geo::{polygon};
201
            use geoengine_expression_deps::*;
202

203
            #[unsafe(no_mangle)]
204
            pub extern "Rust" fn area_of_polygon() -> Option<f64> {
205
                let polygon = MultiPolygon::from(polygon![
206
                    (x: 0., y: 0.),
207
                    (x: 5., y: 0.),
208
                    (x: 5., y: 6.),
209
                    (x: 0., y: 6.),
210
                    (x: 0., y: 0.),
211
                ]);
212

213
                polygon.area()
214
            }
215
        }
216
        .to_string();
1✔
217

218
        let linked_expression =
1✔
219
            LinkedExpression::new("area_of_polygon", &source_code, &dependencies).unwrap();
1✔
220

221
        assert_eq!(
1✔
222
            unsafe {
1✔
223
                linked_expression
1✔
224
                    .function_nary::<fn() -> Option<f64>>()
1✔
225
                    .unwrap()
1✔
226
            }(),
1✔
227
            Some(30.0)
228
        );
229
    }
1✔
230

231
    #[test]
232
    fn it_compiles_an_expression() {
1✔
233
        use geo::{point, polygon};
234

235
        let dependencies = ExpressionDependencies::new().unwrap();
1✔
236

237
        let ast = ExpressionParser::new(
1✔
238
            &[
1✔
239
                Parameter::MultiPolygon("geom".into()),
1✔
240
                Parameter::Number("zero".into()),
1✔
241
            ],
1✔
242
            DataType::MultiPoint,
1✔
243
        )
244
        .unwrap()
1✔
245
        .parse(
1✔
246
            "expression",
1✔
247
            "
1✔
248
                if zero > 0 {
1✔
249
                    centroid(geom)
1✔
250
                } else {
1✔
251
                    centroid(geom)
1✔
252
                }",
1✔
253
        )
254
        .unwrap();
1✔
255

256
        let linked_expression = LinkedExpression::from_ast(&ast, &dependencies).unwrap();
1✔
257

258
        assert_eq!(
1✔
259
            unsafe {
1✔
260
                linked_expression
1✔
261
                    .function_nary::<fn(MultiPolygon, f64) -> Option<MultiPoint>>()
1✔
262
                    .unwrap()
1✔
263
            }(
1✔
264
                MultiPolygon::from(polygon![
1✔
265
                    (x: 0., y: 0.),
1✔
266
                    (x: 5., y: 0.),
1✔
267
                    (x: 5., y: 6.),
1✔
268
                    (x: 0., y: 6.),
1✔
269
                    (x: 0., y: 0.),
1✔
270
                ]),
1✔
271
                0.
1✔
272
            ),
1✔
273
            Some(MultiPoint::from(point!(x: 2.5, y: 3.0)))
1✔
274
        );
275
    }
1✔
276
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc