Prechádzať zdrojové kódy

tests(e2e): add updater integration test (#3973)

Lucas Fernandes Nogueira 3 rokov pred
rodič
commit
ad1786178a

+ 89 - 0
.github/workflows/covector-version-or-publish.yml

@@ -10,6 +10,91 @@ on:
       - dev
 
 jobs:
+  run-integration-tests:
+    runs-on: ${{ matrix.platform }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        platform: [ubuntu-latest, macos-latest, windows-latest]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+      - name: install stable
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+      - name: install webkit2gtk (ubuntu only)
+        if: matrix.platform == 'ubuntu-latest'
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libayatana-appindicator3-dev librsvg2-dev patchelf
+
+      - name: Get current date
+        run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
+        if: matrix.platform == 'macos-latest' || matrix.platform == 'ubuntu-latest'
+
+      - name: Get current date
+        if: matrix.platform == 'windows-latest'
+        run: echo "CURRENT_DATE=$(Get-Date -Format "yyyy-MM-dd")" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+
+      - name: Cache cargo state
+        uses: actions/cache@v2
+        env:
+          cache-name: cargo-state
+        with:
+          path: |
+            ~/.cargo/registry
+            ~/.cargo/git
+            ~/.cargo/bin
+          key: ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }}
+          restore-keys: |
+            ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('**/Cargo.toml') }}-
+            ${{ matrix.platform }}-stable-${{ env.cache-name }}-
+            ${{ matrix.platform }}-stable-
+            ${{ matrix.platform }}-
+
+      - name: Cache core cargo target
+        uses: actions/cache@v2
+        env:
+          cache-name: cargo-core
+        with:
+          path: target
+          # Add date to the cache to keep it up to date
+          key: ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('core/**/Cargo.toml') }}-${{ env.CURRENT_DATE }}
+          # Restore from outdated cache for speed
+          restore-keys: |
+            ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('core/**/Cargo.toml') }}
+            ${{ matrix.platform }}-stable-${{ env.cache-name }}-
+            ${{ matrix.platform }}-stable-
+            ${{ matrix.platform }}-
+
+      - name: Cache CLI cargo target
+        uses: actions/cache@v2
+        env:
+          cache-name: cargo-cli
+        with:
+          path: tooling/cli/target
+          # Add date to the cache to keep it up to date
+          key: ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('tooling/cli/Cargo.lock') }}-${{ env.CURRENT_DATE }}
+          # Restore from outdated cache for speed
+          restore-keys: |
+            ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('tooling/cli/Cargo.lock') }}
+            ${{ matrix.platform }}-stable-${{ env.cache-name }}-
+            ${{ matrix.platform }}-stable-
+            ${{ matrix.platform }}-
+
+      - name: build CLI
+        uses: actions-rs/cargo@v1
+        with:
+          command: build
+          args: --manifest-path ./tooling/cli/Cargo.toml
+
+      - name: run integration tests
+        run: cargo test -- --ignored
+
   version-or-publish:
     runs-on: ubuntu-latest
     timeout-minutes: 65
@@ -17,6 +102,8 @@ jobs:
       change: ${{ steps.covector.outputs.change }}
       commandRan: ${{ steps.covector.outputs.commandRan }}
       successfulPublish: ${{ steps.covector.outputs.successfulPublish }}
+    needs:
+      - run-integration-tests
 
     steps:
       - uses: actions/checkout@v2
@@ -35,6 +122,7 @@ jobs:
         run: |
           git config --global user.name "${{ github.event.pusher.name }}"
           git config --global user.email "${{ github.event.pusher.email }}"
+
       - name: covector version or publish (publish when no change files present)
         uses: jbolda/covector/packages/action@covector-v0
         id: covector
@@ -45,6 +133,7 @@ jobs:
           token: ${{ secrets.GITHUB_TOKEN }}
           command: 'version-or-publish'
           createRelease: true
+
       - name: Create Pull Request With Versions Bumped
         if: steps.covector.outputs.commandRan == 'version'
         uses: tauri-apps/create-pull-request@v3.4.1

+ 2 - 1
Cargo.toml

@@ -10,7 +10,8 @@ members = [
   "core/tauri-codegen",
   
   # integration tests
-  "core/tests/restart"
+  "core/tests/restart",
+  "core/tests/app-updater"
 ]
 
 exclude = [

+ 17 - 0
core/tests/app-updater/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "app-updater"
+version = "0.1.0"
+edition = "2021"
+
+[build-dependencies]
+tauri-build = { path = "../../tauri-build", features = [] }
+
+[dependencies]
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+tiny_http = "0.11"
+tauri = { path = "../../tauri", features = ["updater"] }
+
+[features]
+default = ["custom-protocol"]
+custom-protocol = ["tauri/custom-protocol"]

+ 13 - 0
core/tests/app-updater/build.rs

@@ -0,0 +1,13 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use tauri_build::{try_build, Attributes, WindowsAttributes};
+
+fn main() {
+  if let Err(error) = try_build(Attributes::new().windows_attributes(
+    WindowsAttributes::new().window_icon_path("../../../examples/.icons/icon.ico"),
+  )) {
+    panic!("error found during tauri-build: {:#?}", error);
+  }
+}

+ 33 - 0
core/tests/app-updater/src/main.rs

@@ -0,0 +1,33 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+#![cfg_attr(
+  all(not(debug_assertions), target_os = "windows"),
+  windows_subsystem = "windows"
+)]
+
+fn main() {
+  tauri::Builder::default()
+    .setup(|app| {
+      let handle = app.handle();
+      tauri::async_runtime::spawn(async move {
+        match handle.updater().check().await {
+          Ok(update) => {
+            if let Err(e) = update.download_and_install().await {
+              println!("{}", e);
+              std::process::exit(1);
+            }
+            std::process::exit(0);
+          }
+          Err(e) => {
+            println!("{}", e);
+            std::process::exit(1);
+          }
+        }
+      });
+      Ok(())
+    })
+    .run(tauri::generate_context!())
+    .expect("error while running tauri application");
+}

+ 32 - 0
core/tests/app-updater/tauri.conf.json

@@ -0,0 +1,32 @@
+{
+  "build": {
+    "distDir": [],
+    "devPath": []
+  },
+  "tauri": {
+    "bundle": {
+      "active": true,
+      "targets": "all",
+      "identifier": "com.tauri.updater",
+      "icon": [
+        "../../../examples/.icons/32x32.png",
+        "../../../examples/.icons/128x128.png",
+        "../../../examples/.icons/128x128@2x.png",
+        "../../../examples/.icons/icon.icns",
+        "../../../examples/.icons/icon.ico"
+      ],
+      "category": "DeveloperTool"
+    },
+    "allowlist": {
+      "all": false
+    },
+    "updater": {
+      "active": true,
+      "dialog": false,
+      "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
+      "endpoints": [
+        "http://localhost:3007"
+      ]
+    }
+  }
+}

+ 233 - 0
core/tests/app-updater/tests/update.rs

@@ -0,0 +1,233 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+#![allow(dead_code, unused_imports)]
+
+use std::{
+  collections::HashMap,
+  fs::File,
+  path::{Path, PathBuf},
+  process::Command,
+};
+
+use serde::Serialize;
+
+const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg==";
+
+#[derive(Serialize)]
+struct PackageConfig {
+  version: &'static str,
+}
+
+#[derive(Serialize)]
+struct Config {
+  package: PackageConfig,
+}
+
+#[derive(Serialize)]
+struct PlatformUpdate {
+  signature: String,
+  url: &'static str,
+}
+
+#[derive(Serialize)]
+struct Update {
+  version: &'static str,
+  platforms: HashMap<String, PlatformUpdate>,
+}
+
+fn get_cli_bin_path(cli_dir: &Path, debug: bool) -> Option<PathBuf> {
+  let mut cli_bin_path = cli_dir.join(format!(
+    "target/{}/cargo-tauri",
+    if debug { "debug" } else { "release" }
+  ));
+  if cfg!(windows) {
+    cli_bin_path.set_extension("exe");
+  }
+  if cli_bin_path.exists() {
+    Some(cli_bin_path)
+  } else {
+    None
+  }
+}
+
+fn build_app(cli_bin_path: &Path, cwd: &Path, config: &Config, bundle_updater: bool) {
+  let mut command = Command::new(&cli_bin_path);
+  command
+    .arg("build")
+    .arg("--debug")
+    .arg("--config")
+    .arg(serde_json::to_string(config).unwrap())
+    .current_dir(&cwd);
+
+  #[cfg(windows)]
+  command.args(["--bundles", "msi"]);
+  #[cfg(target_os = "linux")]
+  command.args(["--bundles", "appimage"]);
+  #[cfg(target_os = "macos")]
+  command.args(["--bundles", "app"]);
+
+  if bundle_updater {
+    command
+      .env("TAURI_PRIVATE_KEY", UPDATER_PRIVATE_KEY)
+      .env("TAURI_KEY_PASSWORD", "")
+      .args(["--bundles", "updater"]);
+  }
+
+  let status = command
+    .status()
+    .expect("failed to run Tauri CLI to bundle app");
+
+  if !status.code().map(|c| c == 0).unwrap_or(true) {
+    panic!("failed to bundle app {:?}", status.code());
+  }
+}
+
+#[cfg(target_os = "linux")]
+fn bundle_path(root_dir: &Path, version: &str) -> PathBuf {
+  root_dir.join(format!(
+    "target/debug/bundle/appimage/app-updater_{}_amd64.AppImage",
+    version
+  ))
+}
+
+#[cfg(target_os = "macos")]
+fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf {
+  root_dir.join(format!("target/debug/bundle/macos/app-updater.app"))
+}
+
+#[cfg(windows)]
+fn bundle_path(root_dir: &Path, version: &str) -> PathBuf {
+  root_dir.join(format!(
+    "target/debug/bundle/msi/app-updater_{}_x64_en-US.AppImage",
+    version
+  ))
+}
+
+#[cfg(not(windows))]
+#[test]
+#[ignore]
+fn update_app() {
+  let target = tauri::updater::target().expect("running updater test in an unsupported platform");
+  let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+  let root_dir = manifest_dir.join("../../..");
+  let cli_dir = root_dir.join("tooling/cli");
+
+  let cli_bin_path = if let Some(p) = get_cli_bin_path(&cli_dir, false) {
+    p
+  } else {
+    if let Some(p) = get_cli_bin_path(&cli_dir, true) {
+      p
+    } else {
+      let status = Command::new("cargo")
+        .arg("build")
+        .current_dir(&cli_dir)
+        .status()
+        .expect("failed to run cargo");
+      if !status.success() {
+        panic!("failed to build CLI");
+      }
+      get_cli_bin_path(&cli_dir, true).expect("cargo did not build the Tauri CLI")
+    }
+  };
+
+  let mut config = Config {
+    package: PackageConfig { version: "1.0.0" },
+  };
+
+  // bundle app update
+  build_app(&cli_bin_path, &manifest_dir, &config, true);
+
+  let updater_ext = if cfg!(windows) { "zip" } else { "tar.gz" };
+
+  let out_bundle_path = bundle_path(&root_dir, "1.0.0");
+  let signature_path = out_bundle_path.with_extension(format!(
+    "{}.{}.sig",
+    out_bundle_path.extension().unwrap().to_str().unwrap(),
+    updater_ext
+  ));
+  let signature = std::fs::read_to_string(&signature_path)
+    .unwrap_or_else(|_| panic!("failed to read signature file {}", signature_path.display()));
+  let out_updater_path = out_bundle_path.with_extension(format!(
+    "{}.{}",
+    out_bundle_path.extension().unwrap().to_str().unwrap(),
+    updater_ext
+  ));
+  let updater_path = root_dir.join(format!(
+    "target/debug/{}",
+    out_updater_path.file_name().unwrap().to_str().unwrap()
+  ));
+  std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");
+
+  std::thread::spawn(move || {
+    // start the updater server
+    let server = tiny_http::Server::http("localhost:3007").expect("failed to start updater server");
+
+    loop {
+      if let Ok(request) = server.recv() {
+        match request.url() {
+          "/" => {
+            let mut platforms = HashMap::new();
+
+            platforms.insert(
+              target.clone(),
+              PlatformUpdate {
+                signature: signature.clone(),
+                url: "http://localhost:3007/download",
+              },
+            );
+            let body = serde_json::to_vec(&Update {
+              version: "1.0.0",
+              platforms,
+            })
+            .unwrap();
+            let len = body.len();
+            let response = tiny_http::Response::new(
+              tiny_http::StatusCode(200),
+              Vec::new(),
+              std::io::Cursor::new(body),
+              Some(len),
+              None,
+            );
+            let _ = request.respond(response);
+          }
+          "/download" => {
+            let _ = request.respond(tiny_http::Response::from_file(
+              File::open(&updater_path).unwrap_or_else(|_| {
+                panic!("failed to open updater bundle {}", updater_path.display())
+              }),
+            ));
+          }
+          _ => (),
+        }
+      }
+    }
+  });
+
+  config.package.version = "0.1.0";
+
+  // bundle initial app version
+  build_app(&cli_bin_path, &manifest_dir, &config, false);
+
+  let mut binary_cmd = if cfg!(windows) {
+    Command::new(root_dir.join("target/debug/app-updater.exe"))
+  } else if cfg!(target_os = "macos") {
+    Command::new(bundle_path(&root_dir, "0.1.0").join("Contents/MacOS/app-updater"))
+  } else {
+    if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
+      let mut c = Command::new("xvfb-run");
+      c.arg("--auto-servernum")
+        .arg(bundle_path(&root_dir, "0.1.0"));
+      c
+    } else {
+      Command::new(bundle_path(&root_dir, "0.1.0"))
+    }
+  };
+
+  let status = binary_cmd.status().expect("failed to run app");
+
+  if !status.success() {
+    panic!("failed to run app");
+  }
+}