From e1d64043d87750d5ca87befa9578e91cd83a8ca9 Mon Sep 17 00:00:00 2001
From: Marco Kellershoff <1384938+gorillamoe@users.noreply.github.com>
Date: Mon, 14 Oct 2024 03:08:53 +0200
Subject: [PATCH] fix(tmp): use `stdpath('cache')` for tmp files (#284)

* fix(tmp): use `stdpath('cache')` for tmp files

This should fix: #282

* Add windows testrunner dockerfile

* fix(ci): workflow file

* refactor(ci): add optional windows test pipeline

* feat(ci): windows

* fix(fs): join_paths on windows

* fix(ci): windows cache

* fix(fs): gsub returns two params

* debug(ci): windows caching

* feat(tests): windows fs.join_paths

* refactor(ci): tests
---
 .github/workflows/conform-code.yml            | 43 ------------
 .github/workflows/tests.yml                   | 68 +++++++++++++++++++
 .../scripts/engines/javascript/init.lua       | 16 ++---
 lua/kulala/tmp/.gitignore                     |  2 -
 lua/kulala/tmp/scripts/requests/.gitignore    |  1 -
 lua/kulala/utils/fs.lua                       | 41 ++++++++---
 scripts/install-ci-test-requirements.ps1      | 44 ++++++++++++
 scripts/tests.ps1                             |  2 +
 tests/_dockerfiles/windows/Dockerfile         | 46 ++++++++++++-
 tests/util/fs_spec.lua                        | 55 +++++++++++++++
 10 files changed, 252 insertions(+), 66 deletions(-)
 delete mode 100644 .github/workflows/conform-code.yml
 create mode 100644 .github/workflows/tests.yml
 delete mode 100644 lua/kulala/tmp/.gitignore
 delete mode 100644 lua/kulala/tmp/scripts/requests/.gitignore
 create mode 100644 scripts/install-ci-test-requirements.ps1
 create mode 100644 scripts/tests.ps1
 create mode 100644 tests/util/fs_spec.lua

diff --git a/.github/workflows/conform-code.yml b/.github/workflows/conform-code.yml
deleted file mode 100644
index 99acc24..0000000
--- a/.github/workflows/conform-code.yml
+++ /dev/null
@@ -1,43 +0,0 @@
----
-name: Conform Code
-
-on:
-  pull_request:
-    paths:
-      - '**/*.lua'
-    branches:
-      - main
-
-jobs:
-  tests:
-    name: Tests
-    runs-on: ${{ matrix.os }}
-    strategy:
-      fail-fast: false
-      matrix:
-        include:
-          - os: ubuntu-latest
-            container-image: ghcr.io/mistweaverco/kulala-nvim-testrunner:latest
-    container:
-      image: ${{ matrix.container-image }}
-    steps:
-      - uses: actions/checkout@v4
-      - name: Restore cache
-        uses: actions/cache@v4
-        with:
-          path: |
-            .tests
-          key: ${{ runner.os }}-${{ matrix.container-image }}
-
-      - name: Run tests
-        run: ./scripts/tests.sh run
-  lint:
-    name: Lint Code
-    runs-on: ubuntu-latest
-    container:
-      image: ghcr.io/mistweaverco/kulala-nvim-testrunner:latest
-    steps:
-      - uses: actions/checkout@v4
-
-      - name: Run linter
-        run: ./scripts/lint.sh check-code
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..a82511e
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,68 @@
+---
+name: Tests
+
+on:
+  pull_request:
+    paths:
+      - '**/*.lua'
+    branches:
+      - main
+
+jobs:
+  test-linux:
+    name: Test Code on Linux
+    runs-on: ubuntu-latest
+    container:
+      image: ghcr.io/mistweaverco/kulala-nvim-testrunner:latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Restore cache
+        uses: actions/cache@v4
+        with:
+          path: |
+            .tests
+          key: ${{ runner.os }}-tests
+      - name: Run tests
+        run: ./scripts/tests.sh run
+  lint:
+    name: Lint Code
+    runs-on: ubuntu-latest
+    container:
+      image: ghcr.io/mistweaverco/kulala-nvim-testrunner:latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Run linter
+        run: ./scripts/lint.sh check-code
+  test-windows:
+    name: Test Code on Windows
+    runs-on: windows-latest
+    if: ${{ github.event.label.name == 'needs-windows-test' }}
+    steps:
+      - uses: actions/checkout@v4
+      - name: Restore cache
+        id: cache-deps
+        uses: actions/cache@v4
+        with:
+          path: |
+            .tests
+            ~\scoop
+            ~\AppData\Roaming\LJ4W
+            ~\AppData\Roaming\luarocks
+          key: ${{ runner.os }}-tests
+      - name: Check if cache hit
+        if: steps.cache-deps.outputs.cache-hit == 'true'
+        run: echo "GH_CACHE_HIT=true" >> $Env:GITHUB_ENV
+      - name: Install dependencies
+        run: ./scripts/install-ci-test-requirements.ps1
+      - name: Save cache
+        uses: actions/cache/save@v4
+        with:
+          path: |
+            .tests
+            ~\scoop
+            ~\AppData\Roaming\LJ4W
+            ~\AppData\Roaming\luarocks
+          key: ${{ runner.os }}-tests
+      - name: Run tests
+        run: ./scripts/tests.ps1
diff --git a/lua/kulala/parser/scripts/engines/javascript/init.lua b/lua/kulala/parser/scripts/engines/javascript/init.lua
index cfb78d1..f55b87c 100644
--- a/lua/kulala/parser/scripts/engines/javascript/init.lua
+++ b/lua/kulala/parser/scripts/engines/javascript/init.lua
@@ -8,13 +8,12 @@ local NPM_EXISTS = vim.fn.executable("npm") == 1
 local NODE_EXISTS = vim.fn.executable("node") == 1
 local SCRIPTS_DIR = FS.get_scripts_dir()
 local REQUEST_SCRIPTS_DIR = FS.get_request_scripts_dir()
+local SCRIPTS_BUILD_DIR = FS.get_tmp_scripts_build_dir()
 local BASE_DIR = FS.join_paths(SCRIPTS_DIR, "engines", "javascript", "lib")
-local BASE_FILE_PRE_CLIENT_ONLY =
-  FS.join_paths(SCRIPTS_DIR, "engines", "javascript", "lib", "dist", "pre_request_client_only.js")
-local BASE_FILE_PRE = FS.join_paths(SCRIPTS_DIR, "engines", "javascript", "lib", "dist", "pre_request.js")
-local BASE_FILE_POST_CLIENT_ONLY =
-  FS.join_paths(SCRIPTS_DIR, "engines", "javascript", "lib", "dist", "post_request_client_only.js")
-local BASE_FILE_POST = FS.join_paths(SCRIPTS_DIR, "engines", "javascript", "lib", "dist", "post_request.js")
+local BASE_FILE_PRE_CLIENT_ONLY = FS.join_paths(SCRIPTS_BUILD_DIR, "dist", "pre_request_client_only.js")
+local BASE_FILE_PRE = FS.join_paths(SCRIPTS_BUILD_DIR, "dist", "pre_request.js")
+local BASE_FILE_POST_CLIENT_ONLY = FS.join_paths(SCRIPTS_BUILD_DIR, "dist", "post_request_client_only.js")
+local BASE_FILE_POST = FS.join_paths(SCRIPTS_BUILD_DIR, "dist", "post_request.js")
 local FILE_MAPPING = {
   pre_request_client_only = BASE_FILE_PRE_CLIENT_ONLY,
   pre_request = BASE_FILE_PRE,
@@ -23,8 +22,9 @@ local FILE_MAPPING = {
 }
 
 M.install = function()
-  vim.system({ "npm", "install", "--prefix", BASE_DIR }):wait()
-  vim.system({ "npm", "run", "build", "--prefix", BASE_DIR }):wait()
+  FS.copy_dir(BASE_DIR, SCRIPTS_BUILD_DIR)
+  vim.system({ "npm", "install", "--prefix", SCRIPTS_BUILD_DIR }):wait()
+  vim.system({ "npm", "run", "build", "--prefix", SCRIPTS_BUILD_DIR }):wait()
 end
 
 ---@class Scripts
diff --git a/lua/kulala/tmp/.gitignore b/lua/kulala/tmp/.gitignore
deleted file mode 100644
index d6b7ef3..0000000
--- a/lua/kulala/tmp/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!.gitignore
diff --git a/lua/kulala/tmp/scripts/requests/.gitignore b/lua/kulala/tmp/scripts/requests/.gitignore
deleted file mode 100644
index 8b13789..0000000
--- a/lua/kulala/tmp/scripts/requests/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/lua/kulala/utils/fs.lua b/lua/kulala/utils/fs.lua
index 7a41191..acecb7a 100644
--- a/lua/kulala/utils/fs.lua
+++ b/lua/kulala/utils/fs.lua
@@ -37,7 +37,19 @@ M.ps = M.get_path_separator()
 ---@vararg string
 ---@return string
 M.join_paths = function(...)
-  if M.get_os() == "windows" then
+  if M.os == "windows" then
+    for _, v in ipairs({ ... }) do
+      -- if the path contains at least one forward slash,
+      -- then it needs to be converted to backslashes
+      if v:match("/") then
+        local parts = {}
+        for _, p in ipairs({ ... }) do
+          p = p:gsub("/", "\\")
+          table.insert(parts, p)
+        end
+        return table.concat(parts, M.ps)
+      end
+    end
     return table.concat({ ... }, M.ps)
   end
   return table.concat({ ... }, M.ps)
@@ -167,6 +179,14 @@ M.file_exists = function(filename)
   return vim.fn.filereadable(filename) == 1
 end
 
+M.copy_dir = function(source, destination)
+  if M.os == "unix" or M.os == "mac" then
+    vim.system({ "cp", "-r", source .. M.ps .. ".", destination }):wait()
+  elseif M.os == "windows" then
+    vim.system({ "xcopy", "/H", "/E", "/I", source .. M.ps .. "*", destination }):wait()
+  end
+end
+
 M.ensure_dir_exists = function(dir)
   if vim.fn.isdirectory(dir) == 0 then
     vim.fn.mkdir(dir, "p")
@@ -177,7 +197,9 @@ end
 --- @return string
 --- @usage local p = fs.get_plugin_tmp_dir()
 M.get_plugin_tmp_dir = function()
-  local dir = M.join_paths(M.get_plugin_root_dir(), "tmp")
+  local cache = vim.fn.stdpath("cache")
+  ---@cast cache string
+  local dir = M.join_paths(cache, "kulala")
   M.ensure_dir_exists(dir)
   return dir
 end
@@ -187,6 +209,12 @@ M.get_scripts_dir = function()
   return dir
 end
 
+M.get_tmp_scripts_build_dir = function()
+  local dir = M.join_paths(M.get_plugin_tmp_dir(), "scripts", "build")
+  M.ensure_dir_exists(dir)
+  return dir
+end
+
 M.get_tmp_scripts_dir = function()
   local dir = M.join_paths(M.get_plugin_tmp_dir(), "scripts")
   M.ensure_dir_exists(dir)
@@ -346,14 +374,7 @@ end
 ---Clears all cached files
 M.clear_cached_files = function()
   local tmp_dir = M.get_plugin_tmp_dir()
-  local scripts_dir = M.get_tmp_scripts_dir()
-  local request_scripts_dir = M.get_request_scripts_dir()
-  local compiled_pre_request_scripts = M.join_paths(M.get_scripts_dir(), "engines", "javascript", "lib", "dist")
-  local deleted_files = {}
-  deleted_files = vim.tbl_extend("force", deleted_files, M.delete_files_in_directory(tmp_dir))
-  deleted_files = vim.tbl_extend("force", deleted_files, M.delete_files_in_directory(scripts_dir))
-  deleted_files = vim.tbl_extend("force", deleted_files, M.delete_files_in_directory(request_scripts_dir))
-  deleted_files = vim.tbl_extend("force", deleted_files, M.delete_files_in_directory(compiled_pre_request_scripts))
+  local deleted_files = M.delete_files_in_directory(tmp_dir)
   local string_list = vim.fn.join(
     vim.tbl_map(function(file)
       return "- " .. file
diff --git a/scripts/install-ci-test-requirements.ps1 b/scripts/install-ci-test-requirements.ps1
new file mode 100644
index 0000000..82f6783
--- /dev/null
+++ b/scripts/install-ci-test-requirements.ps1
@@ -0,0 +1,44 @@
+$Env:KULALA_ROOT_DIR = (Get-Location).Path
+
+if ($Env:GH_CACHE_HIT -eq $null) {
+  mkdir .tests
+}
+
+cd .tests
+
+Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+
+if ($Env:GH_CACHE_HIT -eq $null) {
+  Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
+  scoop install main/git
+  scoop install main/neovim@0.10.2
+} else {
+  $Env:PATH = "$Env:USERPROFILE\scoop\shims;$Env:USERPROFILE\scoop\apps\git\current\cmd;$Env:USERPROFILE\scoop\apps\neovim\current\bin;$Env:PATH"
+}
+
+if ($Env:GH_CACHE_HIT -eq $null) {
+  Invoke-RestMethod -Uri https://github.com/mistweaverco/luajit-for-win64/archive/refs/tags/v0.0.2.zip -outfile luajit.zip
+  7z x luajit.zip
+  RM luajit.zip
+  cd luajit-for-win64-0.0.2
+  .\luajit-for-win64.cmd
+} else {
+  cd luajit-for-win64-0.0.2
+}
+
+$Env:KULALA_LUA_DIR = (Get-Location).Path
+
+$Env:PATH = "$Env:KULALA_LUA_DIR\tools\cmd;$Env:KULALA_LUA_DIR\tools\PortableGit\mingw64\bin;$Env:KULALA_LUA_DIR\tools\PortableGit\usr\bin;$Env:KULALA_LUA_DIR\tools\mingw\bin;$Env:KULALA_LUA_DIR\lib;$Env:KULALA_LUA_DIR\bin;$Env:APPDATA\LJ4W\LuaRocks\bin;$Env:PATH"
+$Env:LUA_PATH = "$Env:KULALA_LUA_DIR\lua\?.lua;$Env:KULALA_LUA_DIR\lua\?\init.lua;$Env:APPDATA\luarocks\share\lua\5.1\?.lua;$Env:APPDATA\luarocks\share\lua\5.1\?\init.lua;$Env:LUA_PATH"
+$Env:LUA_CPATH = "$Env:APPDATA\luarocks;$Env:APPDATA\luarocks\lib\lua\5.1\?.dll;$Env:LUA_CPATH"
+
+if ($Env:GH_CACHE_HIT -eq $null) {
+  luarocks install --lua-version 5.1 busted
+}
+
+# Persist the Environment Variables
+"PATH=$Env:Path" >> $Env:GITHUB_ENV
+"LUA_PATH=$Env:LUA_PATH" >> $Env:GITHUB_ENV
+"LUA_CPATH=$Env:LUA_CPATH" >> $Env:GITHUB_ENV
+
+cd $Env:KULALA_ROOT_DIR
diff --git a/scripts/tests.ps1 b/scripts/tests.ps1
new file mode 100644
index 0000000..d4774d8
--- /dev/null
+++ b/scripts/tests.ps1
@@ -0,0 +1,2 @@
+nvim --version
+nvim -l tests/minit.lua tests
diff --git a/tests/_dockerfiles/windows/Dockerfile b/tests/_dockerfiles/windows/Dockerfile
index 064b502..0f027c9 100644
--- a/tests/_dockerfiles/windows/Dockerfile
+++ b/tests/_dockerfiles/windows/Dockerfile
@@ -1,4 +1,46 @@
-FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
+# FROM mcr.microsoft.com/windows/nanoserver:20H2-amd64
+FROM mcr.microsoft.com/powershell:lts-windowsservercore-1809
+
+SHELL ["pwsh", "-Command"]
 
 USER ContainerAdministrator
-WORKDIR C:\\Users\\ContainerAdministrator\\AppData\\Local\\nvim
+
+WORKDIR "C:\\kulala.nvim"
+
+RUN Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+RUN Invoke-RestMethod -Uri https://get.scoop.sh -outfile 'install.ps1'
+RUN .\install.ps1 -RunAsAdmin
+
+# required before add extras
+RUN scoop install main/git
+
+# add for extras suggested by neovim
+RUN scoop bucket add extras
+
+RUN scoop install extras/vcredist2022
+RUN scoop install main/neovim@0.10.2
+
+WORKDIR "C:\\luajjt"
+
+RUN Invoke-RestMethod -Uri https://github.com/mistweaverco/luajit-for-win64/archive/refs/tags/v0.0.2.zip -outfile luajit.zip
+RUN 7z x luajit.zip
+
+WORKDIR "C:\\luajjt\luajit-for-win64-0.0.2"
+
+RUN .\luajit-for-win64.cmd
+
+RUN setx /M KULALA_LUA_DIR \"C:\luajjt\luajit-for-win64-0.0.2\"
+
+RUN setx /M PATH \"$Env:KULALA_LUA_DIR\tools\cmd;$Env:KULALA_LUA_DIR\tools\PortableGit\mingw64\bin;$Env:KULALA_LUA_DIR\tools\PortableGit\usr\bin;$Env:KULALA_LUA_DIR\tools\mingw\bin;$Env:KULALA_LUA_DIR\lib;$Env:KULALA_LUA_DIR\bin;$Env:APPDATA\LJ4W\LuaRocks\bin;$Env:path\"
+
+RUN setx /M LUA_PATH \"$Env:KULALA_LUA_DIR\lua\?.lua;$Env:KULALA_LUA_DIR\lua\?\init.lua;$Env:APPDATA\luarocks\share\lua\5.1\?.lua;$Env:APPDATA\luarocks\share\lua\5.1\?\init.lua;$Env:LUA_PATH\"
+RUN setx /M LUA_CPATH \"$Env:APPDATA\luarocks;$Env:APPDATA\luarocks\lib\lua\5.1\?.dll;$Env:LUA_CPATH\"
+
+RUN luarocks install --lua-version 5.1 busted
+
+WORKDIR "C:\\kulala.nvim"
+
+RUN git config --global safe.directory '*'
+RUN git config --global core.autocrlf true
+
+ENTRYPOINT ["pwsh"]
diff --git a/tests/util/fs_spec.lua b/tests/util/fs_spec.lua
new file mode 100644
index 0000000..f68dcde
--- /dev/null
+++ b/tests/util/fs_spec.lua
@@ -0,0 +1,55 @@
+local Fs = require("kulala.utils.fs")
+
+local assert = require("luassert")
+
+describe("kulala.utils.fs", function()
+  -- restore all changed done by luassert before each test run
+  local snapshot
+
+  before_each(function()
+    snapshot = assert:snapshot()
+  end)
+
+  after_each(function()
+    snapshot:revert()
+  end)
+
+  describe("join_paths on windows", function()
+    Fs.os = "windows"
+    Fs.ps = "\\"
+    it("joins mixed on windows", function()
+      local expected = "C:\\a\\b\\c"
+      local actual = Fs.join_paths("C:\\a", "b", "c")
+      assert.are.same(expected, actual)
+    end)
+    it("joins no-mixed on windows", function()
+      local expected = "C:\\a\\b\\c"
+      local actual = Fs.join_paths("C:\\a", "b", "c")
+      assert.are.same(expected, actual)
+    end)
+    it("fixes ps on windows", function()
+      local expected = "C:\\a\\user\\bin\\blah\\blubb"
+      local actual = Fs.join_paths("C:\\a", "user/bin", "blah/blubb")
+      assert.are.same(expected, actual)
+    end)
+  end)
+  describe("join_paths on linux", function()
+    Fs.os = "unix"
+    Fs.ps = "/"
+    it("joins mixed on unix", function()
+      local expected = "/a/b/c"
+      local actual = Fs.join_paths("/a", "b", "c")
+      assert.are.same(expected, actual)
+    end)
+    it("joins no-mixed on unix", function()
+      local expected = "/a/b/c"
+      local actual = Fs.join_paths("/a", "b", "c")
+      assert.are.same(expected, actual)
+    end)
+    it("joins more mixed on unix", function()
+      local expected = "/a/user/bin/blah/blubb"
+      local actual = Fs.join_paths("/a", "user/bin", "blah/blubb")
+      assert.are.same(expected, actual)
+    end)
+  end)
+end)