restart.rs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::io;
  5. use std::path::{Path, PathBuf};
  6. use std::process::Command;
  7. /// Helper for generic catch-all errors.
  8. type Result = std::result::Result<(), Box<dyn std::error::Error>>;
  9. /// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699-
  10. #[cfg(windows)]
  11. const ERROR_PRIVILEGE_NOT_HELD: i32 = 1314;
  12. /// Represents a successfully created symlink.
  13. enum Symlink {
  14. /// Path to the created symlink
  15. Created(PathBuf),
  16. /// A symlink that failed due to missing permissions (Windows).
  17. #[allow(dead_code)]
  18. Privilege,
  19. }
  20. /// Compile the test binary, run it, and compare it with expected output.
  21. ///
  22. /// Failing to create a symlink due to permissions issues is also a success
  23. /// for the purpose of this runner.
  24. fn symlink_runner(create_symlinks: impl Fn(&Path) -> io::Result<Symlink>) -> Result {
  25. let mut compiled_binary = PathBuf::from(env!("OUT_DIR")).join("../../../restart");
  26. if cfg!(windows) {
  27. compiled_binary.set_extension("exe");
  28. }
  29. println!("{compiled_binary:?}");
  30. // set up all the temporary file paths
  31. let temp = tempfile::TempDir::new()?;
  32. let bin = temp.path().canonicalize()?.join("restart.exe");
  33. // copy the built restart test binary to our temporary directory
  34. std::fs::copy(compiled_binary, &bin)?;
  35. if let Symlink::Created(link) = create_symlinks(&bin)? {
  36. // run the command from the symlink, so that we can test if restart resolves it correctly
  37. let mut cmd = Command::new(link);
  38. // add the restart parameter so that the invocation will call tauri::process::restart
  39. cmd.arg("restart");
  40. let output = cmd.output()?;
  41. // run `TempDir` destructors to prevent resource leaking if the assertion fails
  42. drop(temp);
  43. if output.status.success() {
  44. // gather the output into a string
  45. let stdout = String::from_utf8(output.stdout)?;
  46. // we expect the output to be the bin path, twice
  47. assert_eq!(stdout, format!("{bin}\n{bin}\n", bin = bin.display()));
  48. } else if cfg!(all(
  49. target_os = "macos",
  50. not(feature = "process-relaunch-dangerous-allow-symlink-macos")
  51. )) {
  52. // we expect this to fail on macOS without the dangerous symlink flag set
  53. let stderr = String::from_utf8(output.stderr)?;
  54. // make sure it's the error that we expect
  55. assert!(stderr.contains(
  56. "StartingBinary found current_exe() that contains a symlink on a non-allowed platform"
  57. ));
  58. } else {
  59. // we didn't expect the program to fail in this configuration, just panic
  60. panic!("restart integration test runner failed for unknown reason");
  61. }
  62. }
  63. Ok(())
  64. }
  65. /// Cross-platform way to create a symlink
  66. ///
  67. /// Symlinks that failed to create due to permissions issues (like on Windows)
  68. /// are also seen as successful for the purpose of this testing suite.
  69. fn create_symlink(original: &Path, link: PathBuf) -> io::Result<Symlink> {
  70. #[cfg(unix)]
  71. return std::os::unix::fs::symlink(original, &link).map(|()| Symlink::Created(link));
  72. #[cfg(windows)]
  73. return match std::os::windows::fs::symlink_file(original, &link) {
  74. Ok(()) => Ok(Symlink::Created(link)),
  75. Err(e) => match e.raw_os_error() {
  76. Some(ERROR_PRIVILEGE_NOT_HELD) => Ok(Symlink::Privilege),
  77. _ => Err(e),
  78. },
  79. };
  80. }
  81. /// Only use 1 test to prevent cargo from waiting on itself.
  82. ///
  83. /// While not ideal, this is fine because they use the same solution for both cases.
  84. #[test]
  85. fn restart_symlinks() -> Result {
  86. // single symlink
  87. symlink_runner(|bin| {
  88. let mut link = bin.to_owned();
  89. link.set_file_name("symlink");
  90. link.set_extension("exe");
  91. create_symlink(bin, link)
  92. })?;
  93. // nested symlinks
  94. symlink_runner(|bin| {
  95. let mut link1 = bin.to_owned();
  96. link1.set_file_name("symlink1");
  97. link1.set_extension("exe");
  98. create_symlink(bin, link1.clone())?;
  99. let mut link2 = bin.to_owned();
  100. link2.set_file_name("symlink2");
  101. link2.set_extension("exe");
  102. create_symlink(&link1, link2)
  103. })
  104. }