Browse Source

convert jest tests to child_process run script (#2308)

* convert jest tests to child_process run script

* remove vuecli (it freezes), add angular

* run cargo build in correct dir

* add in asserts on fs

* normalize assert for node14

* fix installing empty dependencies

* add messages to asserts

* use test dir to check if running local cli

* try running in parallel

* run in parallel and log output serially

* avoid parallel, skip yarn for now

* add change file

* disable running with npm6 due to vite fail

Co-authored-by: amrbashir <48618675+amrbashir@users.noreply.github.com>
Jacob Bolda 4 years ago
parent
commit
c410e034f7

+ 6 - 0
.changes/cta-test-script.md

@@ -0,0 +1,6 @@
+---
+"create-tauri-app": patch
+"cli.js": patch
+---
+
+Adjust check for `dev` mode and switch CTA test to a script runner. The script gives us more control and better output into any failures.

+ 35 - 39
.github/workflows/test-cta.yml

@@ -3,6 +3,9 @@
 # SPDX-License-Identifier: MIT
 
 name: test create-tauri-app
+env:
+  RUST_BACKTRACE: 1
+  TAURI_RECIPE: 'vanillajs,cra,vite,ngcli'
 
 on:
   workflow_dispatch:
@@ -13,20 +16,16 @@ on:
     paths:
       - "tooling/create-tauri-app/**"
 
-env:
-  RUST_BACKTRACE: 1
-
 jobs:
   create-recipe-with-npm:
-    name: "node@${{ matrix.node }} + npm@${{ matrix.manager }}: ${{ matrix.recipe }}"
+    name: "node@${{ matrix.node }} + npm@${{ matrix.manager }}"
     runs-on: ${{ github.event.inputs.platform || 'ubuntu' }}-latest
 
     strategy:
       fail-fast: false
       matrix:
         node: ["14", "16"]
-        manager: ["6", "7"]
-        recipe: ["vanillajs", "cra", "vite", "ngcli", "svelte", "dominator"]
+        manager: ["7"]
         exclude:
           - node: "16"
             manager: "6"
@@ -62,40 +61,37 @@ jobs:
       - run: yarn test
         working-directory: tooling/create-tauri-app
         env:
-          TAURI_RECIPE: ${{ matrix.recipe }}
           TAURI_RUN_MANAGER: "npm"
 
-  create-recipe-with-yarn:
-    name: "node@${{ matrix.node }} + yarn@1: ${{ matrix.recipe }}"
-    runs-on: ${{ github.event.inputs.platform || 'ubuntu' }}-latest
+  # create-recipe-with-yarn:
+  #   name: "node@${{ matrix.node }} + yarn@1"
+  #   runs-on: ${{ github.event.inputs.platform || 'ubuntu' }}-latest
 
-    strategy:
-      fail-fast: false
-      matrix:
-        node: ["14", "16"]
-        recipe: ["vanillajs", "cra", "vite", "ngcli", "svelte"]
+  #   strategy:
+  #     fail-fast: false
+  #     matrix:
+  #       node: ["14", "16"]
 
-    steps:
-      - uses: actions/checkout@v2
-      - name: install stable
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-      - uses: volta-cli/action@v1
-        with:
-          node-version: ${{ matrix.node }}
-          yarn-version: 1.22.5
-      - name: install webkit2gtk
-        if: (github.event.inputs.platform || 'ubuntu') == 'ubuntu'
-        run: |
-          sudo apt-get update
-          sudo apt-get install -y libgtk-3-dev libgtksourceview-3.0-dev webkit2gtk-4.0 libappindicator3-dev
-      - run: yarn
-        working-directory: tooling/create-tauri-app
-      - run: yarn build
-        working-directory: tooling/create-tauri-app
-      - run: yarn test
-        working-directory: tooling/create-tauri-app
-        env:
-          TAURI_RECIPE: ${{ matrix.recipe }}
-          TAURI_RUN_MANAGER: "yarn"
+  #   steps:
+  #     - uses: actions/checkout@v2
+  #     - name: install stable
+  #       uses: actions-rs/toolchain@v1
+  #       with:
+  #         toolchain: stable
+  #     - uses: volta-cli/action@v1
+  #       with:
+  #         node-version: ${{ matrix.node }}
+  #         yarn-version: 1.22.5
+  #     - name: install webkit2gtk
+  #       if: (github.event.inputs.platform || 'ubuntu') == 'ubuntu'
+  #       run: |
+  #         sudo apt-get update
+  #         sudo apt-get install -y libgtk-3-dev libgtksourceview-3.0-dev webkit2gtk-4.0 libappindicator3-dev
+  #     - run: yarn
+  #       working-directory: tooling/create-tauri-app
+  #     - run: yarn build
+  #       working-directory: tooling/create-tauri-app
+  #     - run: yarn test
+  #       working-directory: tooling/create-tauri-app
+  #       env:
+  #         TAURI_RUN_MANAGER: "yarn"

+ 2 - 2
tooling/cli.js/src/helpers/rust-cli.ts

@@ -57,8 +57,8 @@ export async function runOnRustCli(
       onClose
     )
   } else {
-    if (existsSync(resolve(targetPath, '../bundler'))) {
-      // running local CLI
+    if (existsSync(resolve(targetPath, 'test'))) {
+      // running local CLI since test directory exists
       const cliPath = resolve(targetPath, '../cli.rs')
       spawnSync('cargo', ['build', '--release'], cliPath)
       const localCliPath = resolve(

+ 4 - 5
tooling/create-tauri-app/package.json

@@ -29,7 +29,7 @@
     "lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts npm yarn",
     "format": "prettier --write --end-of-line=auto \"./**/*.{cjs,js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
     "format:check": "prettier --check --end-of-line=auto \"./**/*.{cjs,js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
-    "test": "jest --runInBand"
+    "test": "node ./test/spawn.test.mjs"
   },
   "dependencies": {
     "chalk": "4.1.1",
@@ -39,16 +39,17 @@
     "scaffe": "1.1.0"
   },
   "devDependencies": {
+    "@effection/process": "2.0.0-beta.8",
     "@rollup/plugin-commonjs": "19.0.0",
     "@rollup/plugin-node-resolve": "13.0.0",
     "@rollup/plugin-typescript": "8.2.1",
     "@types/cross-spawn": "6.0.2",
     "@types/inquirer": "7.3.1",
-    "@types/jest": "26.0.23",
     "@types/minimist": "1.2.1",
     "@types/semver": "7.3.6",
     "@typescript-eslint/eslint-plugin": "4.25.0",
     "@typescript-eslint/parser": "4.25.0",
+    "effection": "2.0.0-beta.8",
     "eslint": "7.27.0",
     "eslint-config-prettier": "8.3.0",
     "eslint-config-standard-with-typescript": "20.0.0",
@@ -57,11 +58,9 @@
     "eslint-plugin-node": "11.1.0",
     "eslint-plugin-promise": "5.1.0",
     "eslint-plugin-security": "1.4.0",
-    "fixturez": "1.1.0",
-    "jest": "27.0.3",
     "prettier": "2.3.0",
     "rollup": "2.50.4",
-    "ts-jest": "27.0.1",
+    "temp-dir": "^2.0.0",
     "tslib": "2.2.0",
     "typescript": "4.3.2"
   }

+ 1 - 1
tooling/create-tauri-app/src/index.ts

@@ -368,7 +368,7 @@ const runInit = async (argv: Argv): Promise<void> => {
     logStep(`Updating ${reset(yellow('"package.json"'))}`)
     updatePackageJson(appDirectory, appName)
 
-    logStep(`Running: ${reset(yellow('tauri init'))}`)
+    logStep(`Running ${reset(yellow('"tauri init"'))}`)
     const binary = !argv.b ? packageManager : resolve(appDirectory, argv.b)
     // pnpm is equivalent to yarn and can run srcipts without using "run" but due to this bug https://github.com/pnpm/pnpm/issues/2764
     // we need to pass "--" to pnpm or arguments won't be parsed correctly so for this command only we are gonna treat pnpm as npm equivalent/

+ 0 - 169
tooling/create-tauri-app/test/index.spec.ts

@@ -1,169 +0,0 @@
-// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-License-Identifier: MIT
-
-import execa from 'execa'
-import fixtures from 'fixturez'
-const f = fixtures(__dirname)
-import path from 'path'
-import fs from 'fs'
-
-const ctaBinary = path.resolve('./bin/create-tauri-app.js')
-const clijs = path.resolve('../cli.js/')
-const api = path.resolve('../api/')
-
-const manager = process.env.TAURI_RUN_MANAGER ?? 'npm'
-const recipes = process.env.TAURI_RECIPE
-  ? [process.env.TAURI_RECIPE]
-  : ['vanillajs', 'cra', 'vite', 'vuecli', 'ngcli', 'svelte', 'dominator']
-const timeoutLong = 900000
-const timeoutLittleLonger = 930000
-const logOut = false ? 'inherit' : 'pipe'
-
-describe('CTA', () => {
-  console.warn(
-    'NOTE: You need to have installed and built cli.js and api before running the tests.'
-  )
-  describe.each(recipes.map((recipe) => [recipe, 'tauri-app']))(
-    `%s recipe`,
-    (recipe: string, appName: string) => {
-      it(
-        'runs',
-        async () => {
-          // creates a temp folder to run CTA within (this is our cwd)
-          const folder = f.temp()
-          const appFolder = path.join(folder, appName)
-
-          // runs CTA with all args set to avoid any prompts
-          const cta = await execa(
-            'node',
-            [
-              ctaBinary,
-              '--manager',
-              manager,
-              '--recipe',
-              recipe,
-              '--ci',
-              '--dev'
-            ],
-            {
-              all: true,
-              stdio: logOut,
-              cwd: folder,
-              timeout: timeoutLong
-            }
-          )
-
-          // check to make certain it didn't fail anywhere
-          expect(cta.failed).toBe(false)
-          expect(cta.timedOut).toBe(false)
-          expect(cta.isCanceled).toBe(false)
-          expect(cta.killed).toBe(false)
-          expect(cta.signal).toBe(undefined)
-
-          const packageFileInitial: {
-            [k: string]: string | object
-          } = JSON.parse(
-            await fs.promises.readFile(
-              path.join(appFolder, 'package.json'),
-              'utf-8'
-            )
-          )
-          expect(packageFileInitial['name']).toBe(appName)
-
-          // run a tauri build to check if what we produced
-          //  can actually create an app
-          //  TODO long term we will want to hook this up to a real test harness
-          //  and then run that test suite instead
-          let opts: string[] = []
-          if (manager === 'npm') {
-            opts =
-              recipe == 'vuecli'
-                ? ['run', 'tauri:build']
-                : ['run', 'tauri', '--', 'build']
-          } else if (manager === 'yarn') {
-            opts = recipe == 'vuecli' ? ['tauri:build'] : ['tauri', 'build']
-          }
-          const tauriBuild = await execa(manager, opts, {
-            all: true,
-            stdio: logOut,
-            cwd: appFolder,
-            timeout: timeoutLong
-          })
-
-          expect(tauriBuild.failed).toBe(false)
-          expect(tauriBuild.timedOut).toBe(false)
-          expect(tauriBuild.isCanceled).toBe(false)
-          expect(tauriBuild.killed).toBe(false)
-          expect(tauriBuild.signal).toBe(undefined)
-
-          const packageFileOutput: {
-            [k: string]: string | object
-          } = JSON.parse(
-            await fs.promises.readFile(
-              path.join(appFolder, 'package.json'),
-              'utf-8'
-            )
-          )
-          expect(packageFileOutput['name']).toBe(appName)
-
-          const assertCustom: { [k: string]: Function } = {
-            vanillajs: () => {
-              expect(packageFileOutput['scripts']).toMatchObject({
-                tauri: 'tauri'
-              })
-            },
-            cra: () => {
-              expect(packageFileOutput['scripts']).toEqual(
-                expect.objectContaining({
-                  tauri: 'tauri'
-                })
-              )
-            },
-            vite: () => {
-              expect(packageFileOutput['scripts']).toEqual(
-                expect.objectContaining({
-                  tauri: 'tauri'
-                })
-              )
-            },
-            vuecli: () => {
-              expect(packageFileOutput['scripts']).toEqual(
-                expect.objectContaining({
-                  'tauri:build': expect.anything(),
-                  'tauri:serve': expect.anything()
-                })
-              )
-            },
-            ngcli: () => {
-              expect(packageFileOutput['scripts']).toEqual(
-                expect.objectContaining({
-                  ng: 'ng',
-                  tauri: 'tauri'
-                })
-              )
-            },
-            svelte: () => {
-              expect(packageFileOutput['scripts']).toEqual(
-                expect.objectContaining({
-                  tauri: 'tauri'
-                })
-              )
-            },
-            dominator: () => {
-              expect(packageFileOutput['scripts']).toEqual(
-                expect.objectContaining({
-                  tauri: 'tauri'
-                })
-              )
-            }
-          }
-
-          const getCustomAsserts = assertCustom[recipe]
-          if (getCustomAsserts) getCustomAsserts()
-        },
-        timeoutLittleLonger
-      )
-    }
-  )
-})

+ 194 - 0
tooling/create-tauri-app/test/spawn.test.mjs

@@ -0,0 +1,194 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import { main, spawn, sleep } from 'effection'
+import { exec } from '@effection/process'
+import fs from 'fs/promises'
+import path from 'path'
+import crypto from 'crypto'
+import tempDirectory from 'temp-dir'
+
+let assert
+const nodeVersion = process.versions.node.split('.')[0]
+if (nodeVersion === '14') {
+  assert = await import('assert')
+} else {
+  assert = await import('assert/strict')
+}
+
+const ctaBinary = path.resolve('./bin/create-tauri-app.js')
+const clijs = path.resolve('../cli.js/')
+const api = path.resolve('../api/')
+
+const manager = process.env.TAURI_RUN_MANAGER || 'yarn'
+const recipes = process.env.TAURI_RECIPE
+  ? process.env.TAURI_RECIPE.split(',')
+  : ['vanillajs', 'cra', 'vite', 'ngcli']
+const parallelize = process.env.TAURI_RECIPE_PARALLELIZE || false
+
+main(function* start() {
+  const tauriTemp = path.join(
+    tempDirectory,
+    `tauri_${crypto.randomBytes(16).toString('hex')}`
+  )
+
+  try {
+    const appName = 'tauri-app'
+    let output = {}
+    for (let i = 0; i < recipes.length; i++) {
+      const recipe = recipes[i]
+      console.log(`::group::recipe ${recipe}`)
+      console.log(`------------------ ${recipe} started -------------------`)
+      const recipeFolder = path.join(tauriTemp, recipe)
+      const appFolder = path.join(recipeFolder, appName)
+      yield fs.mkdir(recipeFolder, { recursive: true })
+      console.log(`${recipeFolder} created.`)
+
+      // runs CTA with all args set to avoid any prompts
+      const run = `node '${ctaBinary}' --manager ${manager} --recipe ${recipe} --ci --dev`
+      console.log(`[running] ${run}`)
+
+      let opts = []
+      if (manager === 'npm') {
+        opts =
+          recipe == 'vuecli'
+            ? ['run', 'tauri:build']
+            : ['run', 'tauri', '--', 'build']
+      } else if (manager === 'yarn') {
+        opts = recipe == 'vuecli' ? ['tauri:build'] : ['tauri', 'build']
+      }
+
+      if (!parallelize) {
+        const cta = yield exec(run, { cwd: recipeFolder })
+        yield streamLogs(cta)
+        // now it is finished, assert on some things
+        yield assertCTAState({ appFolder, appName })
+
+        const tauriBuild = yield exec(manager, {
+          arguments: opts,
+          cwd: appFolder
+        })
+        yield streamLogs(tauriBuild)
+        // build is complete, assert on some things
+        yield assertTauriBuildState({ appFolder, appName })
+      } else {
+        console.log('running CTA recipe in parallel')
+        output[recipe] = yield spawn(function* () {
+          const childCTA = yield exec(run, { cwd: recipeFolder })
+          const cta = yield captureLogs(childCTA)
+          const childTauriBuild = yield exec(manager, {
+            arguments: opts,
+            cwd: appFolder
+          })
+          const tauriBuild = yield captureLogs(childTauriBuild)
+          return { cta, tauriBuild }
+        })
+      }
+
+      console.log(`------------------ ${recipe} complete -------------------`)
+      console.log('::endgroup::')
+      // sometimes it takes a moment to flush all of the logs
+      // to the console, let things catch up here
+      yield sleep(1000)
+    }
+
+    if (parallelize) {
+      for (let i = 0; i < recipes.length; i++) {
+        const recipe = recipes[i]
+        console.log(`::group::recipe ${recipe} logs`)
+        console.log(
+          `------------------ ${recipe} output start -------------------`
+        )
+        const out = yield output[recipe]
+        console.log(out.cta)
+        console.log(out.tauriBuild)
+        console.log(
+          `------------------ ${recipe} output end -------------------`
+        )
+        console.log('::endgroup::')
+      }
+    }
+  } catch (e) {
+    console.error(e)
+    throw Error(e)
+  } finally {
+    console.log('\nstopping process...')
+    // wait a tick for file locks to be release
+    yield sleep(5000)
+    yield fs.rm(tauriTemp, { recursive: true, force: true })
+    console.log(`${tauriTemp} deleted.`)
+  }
+})
+
+function* streamLogs(child) {
+  yield child.stdout.forEach((data) => {
+    process.stdout.write(data)
+  })
+  yield child.stderr.forEach((data) => {
+    process.stderr.write(data)
+  })
+}
+
+function* captureLogs(child) {
+  let log = ''
+  yield child.stdout.forEach((data) => {
+    log += data
+  })
+  yield child.stderr.forEach((data) => {
+    log += data
+  })
+
+  return log
+}
+
+function* assertCTAState({ appFolder, appName }) {
+  const packageFileInitial = JSON.parse(
+    yield fs.readFile(path.join(appFolder, 'package.json'), 'utf-8')
+  )
+  assert.strictEqual(
+    packageFileInitial.name,
+    appName,
+    `The package.json did not have the name "${appName}".`
+  )
+  assert.strictEqual(
+    packageFileInitial.scripts.tauri,
+    'tauri',
+    `The package.json did not have the tauri script.`
+  )
+}
+
+function* assertTauriBuildState({ appFolder, appName }) {
+  const packageFileOutput = JSON.parse(
+    yield fs.readFile(path.join(appFolder, 'package.json'), 'utf-8')
+  )
+  assert.strictEqual(
+    packageFileOutput.name,
+    appName,
+    `The package.json did not have the name "${appName}".`
+  )
+  assert.strictEqual(
+    packageFileOutput.scripts.tauri,
+    'tauri',
+    `The package.json did not have the tauri script.`
+  )
+
+  const cargoFileOutput = yield fs.readFile(
+    path.join(appFolder, 'src-tauri', 'Cargo.toml'),
+    'utf-8'
+  )
+  assert.strictEqual(
+    cargoFileOutput.startsWith(`[package]\nname = "app"`),
+    true,
+    `The Cargo.toml did not have the name "app".`
+  )
+
+  const tauriTarget = yield fs.readdir(
+    path.join(appFolder, 'src-tauri', 'target')
+  )
+  assert.strictEqual(
+    tauriTarget.includes('release'),
+    true,
+    `The Tauri build does not have a target/release directory.`
+  )
+}

File diff suppressed because it is too large
+ 91 - 741
tooling/create-tauri-app/yarn.lock


Some files were not shown because too many files changed in this diff