1 // This is a script to deploy and execute a binary on an iOS simulator. 2 // The primary use of this is to be able to run unit tests on the simulator and 3 // retrieve the results. 4 // 5 // To do this through Cargo instead, use Dinghy 6 // (https://github.com/snipsco/dinghy): cargo dinghy install, then cargo dinghy 7 // test. 8 9 use std::env; 10 use std::fs::{self, File}; 11 use std::io::Write; 12 use std::path::Path; 13 use std::process; 14 use std::process::Command; 15 16 macro_rules! t { 17 ($e:expr) => (match $e { 18 Ok(e) => e, 19 Err(e) => panic!("{} failed with: {}", stringify!($e), e), 20 }) 21 } 22 23 // Step one: Wrap as an app 24 fn package_as_simulator_app(crate_name: &str, test_binary_path: &Path) { 25 println!("Packaging simulator app"); 26 drop(fs::remove_dir_all("ios_simulator_app")); 27 t!(fs::create_dir("ios_simulator_app")); 28 t!(fs::copy(test_binary_path, 29 Path::new("ios_simulator_app").join(crate_name))); 30 31 let mut f = t!(File::create("ios_simulator_app/Info.plist")); 32 t!(f.write_all(format!(r#" 33 <?xml version="1.0" encoding="UTF-8"?> 34 <!DOCTYPE plist PUBLIC 35 "-//Apple//DTD PLIST 1.0//EN" 36 "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 37 <plist version="1.0"> 38 <dict> 39 <key>CFBundleExecutable</key> 40 <string>{}</string> 41 <key>CFBundleIdentifier</key> 42 <string>com.rust.unittests</string> 43 </dict> 44 </plist> 45 "#, crate_name).as_bytes())); 46 } 47 48 // Step two: Start the iOS simulator 49 fn start_simulator() { 50 println!("Looking for iOS simulator"); 51 let output = t!(Command::new("xcrun").arg("simctl").arg("list").output()); 52 assert!(output.status.success()); 53 let mut simulator_exists = false; 54 let mut simulator_booted = false; 55 let mut found_rust_sim = false; 56 let stdout = t!(String::from_utf8(output.stdout)); 57 for line in stdout.lines() { 58 if line.contains("rust_ios") { 59 if found_rust_sim { 60 panic!("Duplicate rust_ios simulators found. Please \ 61 double-check xcrun simctl list."); 62 } 63 simulator_exists = true; 64 simulator_booted = line.contains("(Booted)"); 65 found_rust_sim = true; 66 } 67 } 68 69 if simulator_exists == false { 70 println!("Creating iOS simulator"); 71 Command::new("xcrun") 72 .arg("simctl") 73 .arg("create") 74 .arg("rust_ios") 75 .arg("com.apple.CoreSimulator.SimDeviceType.iPhone-SE") 76 .arg("com.apple.CoreSimulator.SimRuntime.iOS-10-2") 77 .check_status(); 78 } else if simulator_booted == true { 79 println!("Shutting down already-booted simulator"); 80 Command::new("xcrun") 81 .arg("simctl") 82 .arg("shutdown") 83 .arg("rust_ios") 84 .check_status(); 85 } 86 87 println!("Starting iOS simulator"); 88 // We can't uninstall the app (if present) as that will hang if the 89 // simulator isn't completely booted; just erase the simulator instead. 90 Command::new("xcrun").arg("simctl").arg("erase").arg("rust_ios").check_status(); 91 Command::new("xcrun").arg("simctl").arg("boot").arg("rust_ios").check_status(); 92 } 93 94 // Step three: Install the app 95 fn install_app_to_simulator() { 96 println!("Installing app to simulator"); 97 Command::new("xcrun") 98 .arg("simctl") 99 .arg("install") 100 .arg("booted") 101 .arg("ios_simulator_app/") 102 .check_status(); 103 } 104 105 // Step four: Run the app 106 fn run_app_on_simulator() { 107 println!("Running app"); 108 let output = t!(Command::new("xcrun") 109 .arg("simctl") 110 .arg("launch") 111 .arg("--console") 112 .arg("booted") 113 .arg("com.rust.unittests") 114 .output()); 115 116 println!("status: {}", output.status); 117 println!("stdout --\n{}\n", String::from_utf8_lossy(&output.stdout)); 118 println!("stderr --\n{}\n", String::from_utf8_lossy(&output.stderr)); 119 120 let stdout = String::from_utf8_lossy(&output.stdout); 121 let passed = stdout.lines() 122 .find(|l| 123 (l.contains("PASSED") && 124 l.contains("tests")) || 125 l.contains("test result: ok") 126 ) 127 .unwrap_or(false); 128 129 println!("Shutting down simulator"); 130 Command::new("xcrun") 131 .arg("simctl") 132 .arg("shutdown") 133 .arg("rust_ios") 134 .check_status(); 135 if !passed { 136 panic!("tests didn't pass"); 137 } 138 } 139 140 trait CheckStatus { 141 fn check_status(&mut self); 142 } 143 144 impl CheckStatus for Command { 145 fn check_status(&mut self) { 146 println!("\trunning: {:?}", self); 147 assert!(t!(self.status()).success()); 148 } 149 } 150 151 fn main() { 152 let args: Vec<String> = env::args().collect(); 153 if args.len() != 2 { 154 println!("Usage: {} <executable>", args[0]); 155 process::exit(-1); 156 } 157 158 let test_binary_path = Path::new(&args[1]); 159 let crate_name = test_binary_path.file_name().unwrap(); 160 161 package_as_simulator_app(crate_name.to_str().unwrap(), test_binary_path); 162 start_simulator(); 163 install_app_to_simulator(); 164 run_app_on_simulator(); 165 } 166