Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,18 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config nasm libass-dev fonts-dejavu-core
- name: Install Lua wrapper test tools
if: runner.os != 'macOS' || matrix.arch != 'x86_64'
run: python tools/ci/setup-lua.py

- name: Configure
run: meson setup build ${{ matrix.args }}
- name: Build
run: meson compile -C build
- name: Check Lua wrapper tests are enabled
if: runner.os != 'macOS' || matrix.arch != 'x86_64'
shell: bash
run: meson test -C build --list | grep -q aegisub-wrapper
- name: Test
run: meson test -C build --print-errorlogs
- name: Check binary architecture and runtime dependencies
Expand Down
32 changes: 26 additions & 6 deletions examples/Aegisub/Inspector.moon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-- This library is unlicensed under CC0
local requireffi, ffi, looseVersionCompare
local ffi, looseVersionCompare
versionRecord = '0.7.2'

haveDepCtrl, DependencyControl = pcall require, 'l0.DependencyControl'
Expand All @@ -15,7 +15,6 @@ if haveDepCtrl
feed: "https://raw.githubusercontent.com/TypesettingTools/SubInspector/master/DependencyControl.json",
{
{ "ffi" }
{ "requireffi.requireffi", version: "0.1.1" }
}
} )

Expand All @@ -34,11 +33,10 @@ if haveDepCtrl

return true

ffi, requireffi = versionRecord\requireModules!
ffi = versionRecord\requireModules!

else
ffi = require 'ffi'
requireffi = require 'requireffi.requireffi'
SIVersionCompat = 0x000501

versionComponents = ( version ) ->
Expand Down Expand Up @@ -73,7 +71,29 @@ int si_calculateBounds( void*, SI_Rect*, const int32_t*, const uint32_t
void si_cleanup( void* );
]] )

SubInspector, libraryPath = requireffi( 'SubInspector.Inspector.SubInspector' )
loadLibrary = ->
filename = switch ffi.os
when 'Windows' then 'SubInspector.dll'
when 'OSX' then 'libSubInspector.dylib'
else 'libSubInspector.so'

paths = { }
for template in package.path\gmatch '[^;]+'
template = template\gsub '\\', '/'
root = template\match('^(.-)%?%.lua$') or template\match('^(.-)%?%.moon$')
if root
table.insert paths, root .. 'SubInspector/Inspector/' .. filename
-- Package-manager installs can use the system's native library search path.
table.insert paths, 'SubInspector'

errors = { 'Could not load the SubInspector library:' }
for path in *paths
success, library = pcall ffi.load, path
return library if success
table.insert errors, ' ' .. path .. ': ' .. tostring library
error table.concat errors, '\n'

SubInspector = loadLibrary!

log = ( message, ... ) ->
aegisub.log 2, message .. '\n', ...
Expand Down Expand Up @@ -198,7 +218,7 @@ addStyles = ( line, scriptText, seenStyles ) =>
class Inspector
@version = versionRecord

new: ( subtitles = error( "You must provide the subtitles object." ), fcConfig = libraryPath .. "fonts.conf", fontDir = aegisub.decode_path( '?script/fonts' ), logFunc = log ) =>
new: ( subtitles = error( "You must provide the subtitles object." ), fcConfig, fontDir = aegisub.decode_path( '?script/fonts' ), logFunc = log ) =>

success, message = looseVersionCompare SubInspector.si_getVersion!
assert success, message
Expand Down
16 changes: 16 additions & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,19 @@ render_test = executable('render-test', 'render.c',
include_directories: include_directories('../src'),
link_with: subinspector)
test('render', render_test)

if not meson.is_cross_build()
luajit = find_program('luajit', required: false)
moonc = find_program('moonc', required: false)
if luajit.found() and moonc.found()
wrapper = custom_target('Inspector.lua',
input: '../examples/Aegisub/Inspector.moon',
output: 'Inspector.lua',
command: [moonc, '-o', '@OUTPUT@', '@INPUT@'])
python = import('python').find_installation()
test('aegisub-wrapper', python,
args: [files('wrapper.py'), luajit.full_path(), wrapper,
subinspector.full_path(), files('wrapper.lua')],
depends: subinspector)
endif
endif
123 changes: 123 additions & 0 deletions tests/wrapper.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
local wrapper, include = arg[1], arg[2]
local ffi = require('ffi')

local function load_wrapper(test_ffi, search_path, depctrl)
local registered
local env = setmetatable({
package = {path = search_path},
aegisub = {
decode_path = function() return include .. '/fonts' end,
video_size = function() return nil end,
log = function() end,
},
require = function(name)
if name == 'ffi' then return test_ffi end
if name == 'l0.DependencyControl' and depctrl then
return function(record)
record.requireModules = function(self)
assert(#self[1] == 1 and self[1][1][1] == 'ffi')
return test_ffi
end
record.checkVersion = function() return true end
record.register = function(_, module)
registered = module
return module
end
return record
end
end
error('Unexpected dependency: ' .. name)
end,
}, {__index = _G})
local module = setfenv(assert(loadfile(wrapper)), env)()
if depctrl then assert(registered == module) end
return module
end

-- Exercise all platform names and path conventions without loading foreign
-- binaries. These cases also run without DependencyControl or requireffi.
local cases = {
{'Windows', 'C:\\Users\\Test Person\\include\\?.lua',
'C:/Users/Test Person/include/SubInspector/Inspector/SubInspector.dll'},
{'OSX', '/missing/?.lua;/Application Support/include/?.moon',
'/Application Support/include/SubInspector/Inspector/libSubInspector.dylib'},
{'Linux', '/usr/share/aegisub/automation/include/?.lua',
'/usr/share/aegisub/automation/include/SubInspector/Inspector/libSubInspector.so'},
{'Linux', '/missing/?.lua;/missing/?/init.lua', 'SubInspector'},
}
for _, case in ipairs(cases) do
for _, depctrl in ipairs({false, true}) do
local calls = {}
load_wrapper({
os = case[1],
cdef = function() end,
load = function(path)
calls[#calls + 1] = path
if path == case[3] then return {} end
error('library missing or incompatible')
end,
}, case[2], depctrl)
assert(calls[#calls] == case[3])
for _, path in ipairs(calls) do
assert(not path:find('init.lua', 1, true))
end
end
end

local ok, message = pcall(load_wrapper, {
os = 'OSX',
cdef = function() end,
load = function() error('wrong architecture') end,
}, '/missing/?.lua', false)
assert(not ok)
assert(message:find('/missing/SubInspector/Inspector/libSubInspector.dylib', 1, true))
assert(message:find('SubInspector: ', 1, true))
assert(message:find('wrong architecture', 1, true))

-- Load the real staged library and exercise the wrapper-to-C interface, both
-- with and without DependencyControl registration.
local declared = false
for _, depctrl in ipairs({false, true}) do
local loaded_path, config
local test_ffi = setmetatable({
cdef = function(definitions)
if not declared then ffi.cdef(definitions); declared = true end
end,
load = function(path)
local library = ffi.load(path)
loaded_path = path
return setmetatable({
si_init = function(width, height, font_config, fonts)
config = font_config
return library.si_init(width, height, font_config, fonts)
end,
}, {__index = function(_, key) return library[key] end})
end,
}, {__index = ffi})
local Inspector = load_wrapper(test_ffi, include .. '/?.lua', depctrl)
local expected_root = include:gsub('\\', '/') .. '/SubInspector/Inspector/'
assert(loaded_path:find(expected_root, 1, true) == 1)
local subtitles = {
{class = 'info', key = 'ScriptType', value = 'v4.00+', raw = 'ScriptType: v4.00+'},
{class = 'info', key = 'PlayResX', value = '640', raw = 'PlayResX: 640'},
{class = 'info', key = 'PlayResY', value = '480', raw = 'PlayResY: 480'},
{class = 'style', name = 'Default', raw =
'Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,' ..
'0,0,0,0,100,100,0,0,1,0,0,7,0,0,0,1'},
}
local inspector = Inspector(subtitles)
assert(config == nil and inspector.fcConfig == nil)
local shape = '{\\an7\\pos(100,100)\\p1}m 0 0 l 20 0 20 20 0 20'
local rects = assert(inspector:getBounds({{
style = 'Default', text = shape,
raw = 'Dialogue: 0,0:00:00.00,0:00:02.00,Default,,0,0,0,,' .. shape,
}}, {0, 500}))
assert(#rects == 2)
assert(rects[1].x == 100 and rects[1].y == 100)
assert(rects[1].w == 20 and rects[1].h == 20 and rects[1].solid)
assert(rects[1].hash == rects[2].hash)
local configured = Inspector(subtitles, 'custom-fonts.conf')
assert(config == 'custom-fonts.conf' and configured.fcConfig == config)
end

print('Library discovery, errors, DependencyControl registration, and wrapper rendering passed.')
16 changes: 16 additions & 0 deletions tests/wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Stage a normal Aegisub include directory and run the LuaJIT wrapper tests."""

import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

luajit, wrapper, binary, tests = sys.argv[1:]
with tempfile.TemporaryDirectory(prefix='SubInspector wrapper ') as directory:
include = Path(directory) / 'include'
libraries = include / 'SubInspector' / 'Inspector'
libraries.mkdir(parents=True)
(include / 'fonts').mkdir()
shutil.copy2(binary, libraries / Path(binary).name)
subprocess.run([luajit, tests, wrapper, str(include)], check=True)
51 changes: 51 additions & 0 deletions tools/ci/setup-lua.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Install LuaJIT and MoonScript for the native Aegisub wrapper tests in CI."""

import hashlib
import os
import subprocess
import sys
import urllib.request
import zipfile
from pathlib import Path


def run(*args, **kwargs):
subprocess.run(args, check=True, **kwargs)


temporary = Path(os.environ['RUNNER_TEMP'])
paths = []

if sys.platform == 'linux':
run('sudo', 'apt-get', 'install', '-y', 'luajit', 'luarocks', 'liblua5.1-0-dev')
run('sudo', 'luarocks', '--lua-version=5.1', 'install', 'moonscript', '0.7.0')
elif sys.platform == 'darwin':
run('brew', 'install', 'luajit', 'luarocks')
luajit = subprocess.check_output(['brew', '--prefix', 'luajit'], text=True).strip()
tree = temporary / 'luarocks'
run('luarocks', '--lua-version=5.1', f'--lua-dir={luajit}', f'--tree={tree}',
'install', 'moonscript', '0.7.0')
paths.append(tree / 'bin')
elif sys.platform == 'win32':
luajit = temporary / 'luajit'
run('git', 'clone', '--depth', '1', '--branch', 'v2.1',
'https://github.com/LuaJIT/LuaJIT.git', luajit)
run('cmd', '/c', 'msvcbuild.bat', cwd=luajit / 'src')
paths.append(luajit / 'src')

archive = temporary / 'moonscript.zip'
urllib.request.urlretrieve(
'https://github.com/leafo/moonscript/releases/download/v0.7.0/'
'moonscript-v0.7.0-windows-x86_64.zip', archive)
if hashlib.sha256(archive.read_bytes()).hexdigest() != (
'36c41ae1faaff1423e100df667a498df61607470b0588663adc14e9db536eb17'):
sys.exit('MoonScript archive checksum mismatch')
with zipfile.ZipFile(archive) as compiler:
compiler.extractall(temporary / 'moonscript')
paths.append(temporary / 'moonscript')
else:
sys.exit(f'Unsupported CI platform: {sys.platform}')

with open(os.environ['GITHUB_PATH'], 'a', encoding='utf-8') as github_path:
for path in paths:
github_path.write(f'{path}\n')
Loading