edge_impulse_runner/inference/
mod.rs

1pub mod messages;
2pub mod model;
3// Removed eim_original module - functionality moved to backends/eim.rs
4
5#[cfg(test)]
6mod tests {
7    use crate::{EdgeImpulseError, EdgeImpulseModel};
8    use std::env;
9    use std::fs::File;
10    use std::io::Write;
11    use std::path::Path;
12    use std::process::Command;
13    use tempfile;
14
15    /// Creates a mock EIM executable for testing
16    ///
17    /// This function creates a shell script that simulates an EIM model by:
18    /// 1. Accepting a socket path argument
19    /// 2. Creating a Unix socket at that path using socat
20    /// 3. Responding to the hello message with a valid JSON response
21    fn create_mock_eim() -> std::path::PathBuf {
22        let manifest_dir =
23            env::var("CARGO_MANIFEST_DIR").expect("Failed to get manifest directory");
24        let mock_path = Path::new(&manifest_dir).join("mock_eim.sh");
25        let response_path = Path::new(&manifest_dir).join("mock_response.json");
26
27        // Create the response JSON file
28        let response_json = r#"{"success":true,"id":1,"model_parameters":{"axis_count":3,"frequency":62.5,"has_anomaly":1,"image_channel_count":0,"image_input_frames":0,"image_input_height":0,"image_input_width":0,"image_resize_mode":"none","inferencing_engine":4,"input_features_count":375,"interval_ms":16,"label_count":6,"labels":["drink","fistbump","idle","snake","updown","wave"],"model_type":"classification","sensor":2,"slice_size":31,"threshold":0.6,"use_continuous_mode":false},"project":{"deploy_version":271,"id":1,"name":"Test Project","owner":"Test Owner"}}"#;
29        std::fs::write(&response_path, response_json).unwrap();
30
31        // Create the mock script that reads from the response file
32        let mock_script = format!(
33            r#"#!/bin/sh
34SOCKET_PATH=$1
35socat UNIX-LISTEN:$SOCKET_PATH,fork SYSTEM:'cat {}'"#,
36            response_path.display()
37        );
38
39        let mut file = File::create(&mock_path).unwrap();
40        file.write_all(mock_script.as_bytes()).unwrap();
41
42        // Make the script executable
43        use std::os::unix::fs::PermissionsExt;
44        let mut perms = std::fs::metadata(&mock_path).unwrap().permissions();
45        perms.set_mode(0o755);
46        std::fs::set_permissions(&mock_path, perms).unwrap();
47
48        mock_path
49    }
50
51    #[test]
52    fn test_missing_file_error() {
53        // Create a temporary directory for the socket
54        let temp_dir = tempfile::tempdir().unwrap();
55        let socket_path = temp_dir.path().join("test.socket");
56
57        // Test with a non-existent file
58        let result = EdgeImpulseModel::new_with_socket(
59            std::path::Path::new("unknown.eim"),
60            socket_path.as_path(),
61        );
62        match result {
63            Err(EdgeImpulseError::ExecutionError(msg)) if msg.contains("No such file") => (),
64            other => panic!("Expected ExecutionError for missing file, got {:?}", other),
65        }
66    }
67
68    #[test]
69    fn test_invalid_extension() {
70        // Verify that attempting to load a file without .eim extension returns InvalidPath
71        let temp_file = std::env::temp_dir().join("test.txt");
72        std::fs::write(&temp_file, "dummy content").unwrap();
73
74        let result = EdgeImpulseModel::new(temp_file.as_path());
75        match result {
76            Err(EdgeImpulseError::InvalidPath) => (),
77            _ => panic!("Expected InvalidPath when file has wrong extension"),
78        }
79    }
80
81    #[test]
82    fn test_successful_connection() {
83        // Check if socat is available (required for this test)
84        let socat_check = Command::new("which")
85            .arg("socat")
86            .output()
87            .expect("Failed to check for socat");
88
89        if !socat_check.status.success() {
90            println!("Skipping test: socat is not installed");
91            return;
92        }
93
94        // Create a temporary directory for the socket
95        let temp_dir = tempfile::tempdir().unwrap();
96        let socket_path = temp_dir.path().join("test.socket");
97
98        // Create and set up the mock EIM executable
99        let mock_path = create_mock_eim();
100        let response_path = mock_path.with_extension("json");
101        let mut mock_path_with_eim = mock_path.clone();
102        mock_path_with_eim.set_extension("eim");
103        std::fs::rename(&mock_path, &mock_path_with_eim).unwrap();
104
105        // Test the connection with the custom socket path
106        let result =
107            EdgeImpulseModel::new_with_socket(mock_path_with_eim.as_path(), socket_path.as_path());
108        assert!(
109            result.is_ok(),
110            "Failed to create EIM model: {:?}",
111            result.err()
112        );
113
114        // Clean up the test files
115        if mock_path_with_eim.exists() {
116            std::fs::remove_file(&mock_path_with_eim).unwrap_or_else(|e| {
117                println!("Warning: Failed to remove mock EIM file: {}", e);
118            });
119        }
120        if response_path.exists() {
121            std::fs::remove_file(&response_path).unwrap_or_else(|e| {
122                println!("Warning: Failed to remove response file: {}", e);
123            });
124        }
125    }
126
127    #[test]
128    fn test_connection_timeout() {
129        // Create a temporary directory
130        let temp_dir = tempfile::tempdir().unwrap();
131        let socket_path = temp_dir.path().join("test.socket");
132        let model_path = temp_dir.path().join("dummy.eim");
133
134        // Create the executable
135        let script = "#!/bin/sh\nsleep 10\n"; // Sleep long enough for timeout
136        std::fs::write(&model_path, script).unwrap();
137
138        #[cfg(unix)]
139        {
140            use std::os::unix::fs::PermissionsExt;
141            let mut perms = std::fs::metadata(&model_path).unwrap().permissions();
142            perms.set_mode(0o755);
143            std::fs::set_permissions(&model_path, perms).unwrap();
144        }
145
146        // Test that we get the expected timeout error
147        let result = EdgeImpulseModel::new_with_socket(model_path.as_path(), socket_path.as_path());
148        assert!(
149            matches!(result,
150                Err(EdgeImpulseError::SocketError(ref msg)) if msg.contains("Timeout waiting for socket")
151            ),
152            "Expected timeout error, got {:?}",
153            result
154        );
155    }
156}