Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .busted
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
return {
_all = {
lua = 'nlua',
lpath = "lua/?.lua;lua/?/init.lua",
},
unit = {
ROOT = {'./test/unit/'},
},
functional = {
ROOT = {'./test/e2e/'},
pattern = '', -- No fancy names for E2E tests
},
}

25 changes: 25 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: Run tests
on:
pull_request: ~
push:
branches:
- main

jobs:
build:
name: Run tests
runs-on: ubuntu-latest
strategy:
matrix:
neovim_version: ['nightly', 'stable']

steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: "1.10"
- name: Run tests
uses: nvim-neorocks/nvim-busted-action@v1
with:
nvim_version: ${{ matrix.neovim_version }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ tags
[._]*.un~

# End of https://www.toptal.com/developers/gitignore/api/lua,vim

spec/smuggler/julia_test_env
spec/xdg/local/state
7 changes: 6 additions & 1 deletion .luacheckrc
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
globals = { "vim" }
read_globals = {
"vim",
"describe",
"it",
"assert"
}
21 changes: 21 additions & 0 deletions nvim-smuggler-scm-1.rockspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
rockspec_format = '3.0'
package = 'nvim-smuggler'
version = 'scm-1'

dependencies = {
'lua >= 5.1',
'nvim-nio',
}

test_dependencies = {
'nlua',
'pathlib.nvim',
}

source = {
url = 'git://github.com/klafyvel/' .. package,
}

build = {
type = 'builtin',
}
101 changes: 101 additions & 0 deletions spec/smuggler/e2e/eval_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
local julia_manager = require("spec.smuggler.manage_julia")
local nvim_manager = require("spec.smuggler.manage_remote_nvim")

describe("Simple smuggling of code.", function()
local nvim -- Channel of the embedded Neovim process
local julia -- Channel of the embedded Julia process
local buf -- Pointer to the current buffer
local win -- Pointer to the current Window

describe("Base operations.", function()
setup(function()
-- Start a new Neovim process
nvim = nvim_manager.create_nvim_instance()
julia = julia_manager.start_repl_process()
buf = vim.rpcrequest(nvim, "nvim_create_buf", true, false)
vim.rpcrequest(nvim, "nvim_command", "set syntax=julia")
win = vim.rpcrequest(nvim, "nvim_get_current_win")
vim.rpcrequest(nvim, "nvim_win_set_buf", win, buf)
local code_snippet = [[
println("Print on the first line")

1 + 1

a = 1
b = 2
c = 3

error("This error is on line 9")
]]
vim.rpcrequest(nvim, "nvim_buf_set_lines", buf, 0, -1, false, vim.split(code_snippet, "\n"))
local cursor_pos = { 1, 0 }
vim.rpcrequest(nvim, "nvim_win_set_cursor", win, cursor_pos)
vim.rpcrequest(
nvim,
"nvim_command",
'lua require("smuggler").setup({buffers={availablesockets="'
.. require("spec.smuggler.manage_julia").JULIA_SOCKET
.. '"}})'
)
end)
teardown(function()
-- Terminate the Neovim process
nvim_manager.terminate_nvim_instance(nvim)
julia_manager.terminate_repl_process(julia)
end)
it("Can connect to the Julia REPL", function()
vim.rpcrequest(nvim, "nvim_command", "SmuggleConfig")
-- REPLSmuggler.jl prints a newline character here for some reason...
assert.is.equal("", julia.readline(1000))
julia.write("println(length(REPLSmuggler.CURRENT_SMUGGLER.sessions))\n")
local number_connected_sessions = tonumber(julia.readline(10000))
assert.is.equal(1, number_connected_sessions)
assert.is.equal("", julia.readline(1000))
end)
it("Can send code snippets to julia", function()
assert.is.equal(true, julia.handle:is_active())
-- vim.rpcrequest(nvim, "nvim_command", "Smuggle")
local line = vim.rpcrequest(nvim, "nvim_get_current_line")
vim.print(line)
julia.write("Base.active_repl\n")
vim.print(julia.readline_err(1000))
vim.print(julia.readline_err(1000))
vim.print(julia.readline_err(1000))
local printresult = julia.readline(5000)
assert.is.equal("Print on the first line", printresult)
end)
-- pending("Can send a range")
-- pending("Can send visual selection")
-- pending("Can be used as an operator")
-- pending("Can reconnect to the REPL")
-- pending("Can interrupt a command")
-- pending("Can exit a session")
-- pending("Can exit a session")
-- pending("Can re-connect to a session")
-- pending("Gives the correct source file for the smuggled code")
-- pending("Can report Julia exception")
-- pending("Can evaluate by blocks")
-- pending("Can toggle diagnostics")
-- pending("Can toggle quickfix")
-- pending("Can toggle evaluated chunks")
-- pending("Reports the correct evaluation results")
-- pending("Can toggle the display of evaluation results")
-- pending("Can display image results")
end)

-- describe("Fix issue #27: Include all stacktrace entries in the list", function()
-- pending("Includes all stacktrace entries in the list.")
-- end)
-- describe("Fix issue #35: Ablility to send single character chunks", function()
-- pending("Can send single character chunks")
-- end)
-- describe("Fix issue #39: Code invalidation in insert mode", function()
-- pending("Invalidates the correct code when editing in insert mode")
-- end)
-- describe("Fix issue #52: Graceful exit", function()
-- pending("Does not leave undesired things in the buffer after exiting.")
-- end)
-- describe("Fix issue #55: Do not crash when deleting single-line chunks", function()
-- pending("Does not crash when a single-line chunk gets deleted.")
-- end)
end)
170 changes: 170 additions & 0 deletions spec/smuggler/manage_julia.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
local M = {}
local nio = require("nio")
local Path = require("pathlib")

M.JULIA_COMMAND = string.gsub(vim.system({ "which", "julia" }, { text = true }):wait().stdout, "\n$", "")
M.JULIA_ENV_PATH = Path.cwd() / "spec" / "smuggler" / "julia_test_env"
M.JULIA_SOCKET = (M.JULIA_ENV_PATH / "test_socket"):tostring()

function M.run_single_julia_command(cmd)
return vim.system({ M.JULIA_COMMAND, "--project=" .. M.JULIA_ENV_PATH:tostring(), "-e", cmd }, { text = true })
:wait()
end

function M.instantiate_env()
M.JULIA_ENV_PATH:mkdir(Path.permission("rwxr-xr-x"), true)
return M.run_single_julia_command('import Pkg;Pkg.add(Pkg.PackageSpec(name="REPLSmuggler", rev="main"))')
end

function M.start_repl_process()
vim.notify("Starting Julia REPL...", vim.log.levels.INFO)
M.instantiate_env()
local stdin = vim.uv.new_pipe()
local stdout = vim.uv.new_pipe()
local stderr = vim.uv.new_pipe()
local handle, pid = vim.uv.spawn(M.JULIA_COMMAND, {
stdio = { stdin, stdout, stderr },
args = { "-i", "--banner=no", "--project=" .. M.JULIA_ENV_PATH:tostring() },
cwd = M.JULIA_ENV_PATH:tostring(),
}, function(code, signal)
print("Julia process exited. Code ", code, ", signal ", signal, ".")
end)
vim.notify("Julia REPL started.\n", vim.log.levels.DEBUG)
local function close()
stdin:shutdown(function(_)
handle:close()
end)
stdin:close()
stdout:close()
stderr:close()
end
local function write(s)
stdin:write(s)
end
local readline_buffer = ""
local function readline(timeout)
local result = nil
if timeout == nil then
timeout = 1000
end
local match = string.match(readline_buffer, "^([^\n]*)\n")
if match ~= nil then
result = match
local remaining_buffer_length = match:len() - readline_buffer:len() + 1
if remaining_buffer_length == 0 then
readline_buffer = ""
else
readline_buffer = readline_buffer:sub(remaining_buffer_length)
end
else
stdout:read_start(function(err, data)
if err then
-- todo: handle errors
elseif data then
readline_buffer = readline_buffer .. data
local match = string.match(readline_buffer, "^([^\n]*)\n")
vim.print("Step, buffer=" .. vim.inspect(readline_buffer) .. " match=" .. vim.inspect(match))
if match ~= nil then
result = match
local remaining_buffer_length = match:len() - readline_buffer:len() + 1
if remaining_buffer_length == 0 then
readline_buffer = ""
else
readline_buffer = readline_buffer:sub(remaining_buffer_length)
end
stdout:read_stop()
end
end
end)
local waitsuccess = vim.wait(timeout, function()
return not stdout:is_active()
end)
if not waitsuccess then
vim.notify(
"Waiting for stdout timeouted. Buffer is: " .. vim.inspect(readline_buffer),
vim.log.levels.WARN
)
end
stdout:read_stop()
end
vim.print("Buffer=" .. vim.inspect(readline_buffer) .. ", result=" .. vim.inspect(result))
return result
end
local readline_err_buffer = ""
local function readline_err(timeout)
local result = nil
if timeout == nil then
timeout = 1000
end
local match = string.match(readline_err_buffer, "^([^\n]*)\n")
if match ~= nil then
result = match
local remaining_buffer_length = match:len() - readline_err_buffer:len() + 1
if remaining_buffer_length == 0 then
readline_err_buffer = ""
else
readline_err_buffer = readline_err_buffer:sub(remaining_buffer_length)
end
else
stderr:read_start(function(err, data)
if err then
-- todo: handle errors
elseif data then
readline_err_buffer = readline_err_buffer .. data
local match = string.match(readline_err_buffer, "^([^\n]*)\n")
if match ~= nil then
result = match
local remaining_buffer_length = match:len() - readline_err_buffer:len() + 1
if remaining_buffer_length == 0 then
readline_err_buffer = ""
else
readline_err_buffer = readline_err_buffer:sub(remaining_buffer_length)
end
stderr:read_stop()
end
end
end)
local waitsuccess = vim.wait(timeout, function()
return not stderr:is_active()
end)
if not waitsuccess then
vim.notify("Waiting for stderr timeouted.\n", vim.log.levels.WARN)
end
stderr:read_stop()
end
return result
end
local obj = {
close = close,
write = write,
readline = readline,
readline_err = readline_err,
handle = handle,
}
obj.write([[
using REPLSmuggler
smuggle("test_socket", basepath=pwd())
]])
assert(obj.readline_err(10000) ~= nil, "REPLSmuggler did not start in time.") -- REPLSmuggler server starting
assert(obj.readline(10000) ~= nil, "REPLSmuggler did not start in time.") -- REPLSmuggler server starting
return obj
end

function M.cleanup_julia_directory()
for path in M.JULIA_ENV_PATH:fs_iterdir() do
if path:basename() ~= "Manifest.toml" and path:basename() ~= "Project.toml" then
if path:is_dir() then
vim.fn.delete(path:tostring())
else
path:unlink()
end
end
end
end

function M.terminate_repl_process(repl)
repl:close()
M.cleanup_julia_directory()
end

return M
26 changes: 26 additions & 0 deletions spec/smuggler/manage_remote_nvim.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
local M = {}

local jobopts = {
rpc = true,
width = 80,
height = 24,
env = {
XDG_DATA_HOME = vim.fn.getcwd() .. "/spec/xdg/local/share",
XDG_STATE_HOME = vim.fn.getcwd() .. "/spec/xdg/local/state",
XDG_CONFIG_HOME = vim.fn.getcwd() .. "/spec/xdg/config",
},
}
local config_link = jobopts.env.XDG_DATA_HOME .. "/nvim/site/pack/testing/start/smuggler"

function M.create_nvim_instance()
vim.uv.fs_symlink(vim.fn.getcwd(), config_link)
local nvim = vim.fn.jobstart({ vim.v.progpath, "--embed", "--headless" }, jobopts)
return nvim
end

function M.terminate_nvim_instance(nvim)
vim.fn.jobstop(nvim)
vim.uv.fs_unlink(config_link)
end

return M
Empty file added spec/xdg/config/init.lua
Empty file.
Loading