Презентация для выступления на Владивостокском митапе https://moscowdjango.timepad.ru/event/1884275/
"Основной режим разработки приложений в Тарантуле — это написание скриптов и манипуляции данными на Lua (оставим пока за скобками режим SQL). Часто внедрению Тарантула в некую систему становится блокером использование Lua.
В экосистеме Lua мало тулинга, это касается как встроенного интерпретатора, так и JIT-транслятора LuaJIT.
Давайте оглядимся, как обстоят дела с тестированием, профилированием, статическим анализом и дебагом в Lua. И как это все может быть использовано при разработке сервисов, в архитектуре которых есть Tarantool."
Инструменты для з̶а̶х̶в̶а̶т̶а̶ ̶м̶и̶р̶а̶ отладки в Tarantool
1.
2.
3.
4. Мало и для
vanilla Lua
и для LuaJIT
Хорошо в
игростроении
Отсутствие
тулинга >
ограничивает
доступную
экспертизу
Мало тулинга в
экосистеме Луа
5. • Поддержка синтаксиса, навигации и рефакторинг в редакторе;
• Пакетный менеджер и живое сообщество вокруг него;
• Отладчик, желательно в интегрированной среде;
• Инструменты для анализа производительности, желательно -
доступные из редактора;
• Тестирование, желательно из редактора;
• Статический анализатор;
• Фаззинг.
8. • Очень быстрая NoSQL СУБД с сервером приложений
• Открытый код ядра, есть компоненты closed source (в
коммерческой версии)
• Способность обрабатывать огромное кол-во одновременных
подключений
• Соответствие ACID
• Отказоустойчивость и масштабируемость (репликация,
шардинг)
• Кооперативная многозадачность, неблокирующие операции
IO (включая работу с внешними сервисами и файловой
системой)
9.
10. Файбер (fiber) – он как сопрограмма
(coroutine) только файбер
• Файбер (fiber)
• Легковесная нить исполнения, реализующая
кооперативную многозадачность
• Кооперативная многозадачность
• Следующая задача выполняется после того, как
текущая объявит о передаче управления
25. describe('Busted unit testing framework', function()
describe('should be awesome', function()
it('should be easy to use', function()
assert.truthy('Yup.')
end)
it('should have lots of features', function()
-- deep check comparisons!
assert.same({ table = 'great'}, { table = 'great' })
-- or check by reference!
assert.is_not.equals({ table = 'great'}, { table = 'great'})
assert.falsy(nil)
assert.error(function() error('Wat') end)
end)
it('should provide some shortcuts to common functions', function()
assert.unique({{ thing = 1 }, { thing = 2 }, { thing = 3 }})
end)
it('should have mocks and spies for functional tests', function()
local thing = require('thing_module')
spy.spy_on(thing, 'greet')
thing.greet('Hi!')
http://olivinelabs.com/busted/
26. #!/usr/bin/tarantool
local tap = require('tap')
test = tap.test("my test name")
test:plan(2)
test:ok(2 * 2 == 4, "2 * 2 is 4")
test:test("some subtests for test2", function(test)
test:plan(2)
test:is(2 + 2, 4, "2 + 2 is 4")
test:isnt(2 + 3, 4, "2 + 3 is not 4")
end)
test:check()
Tarantool
https://www.tarantool.io/ru/d
oc/latest/reference/reference
_lua/tap/
27. -- test/feature_test.lua
local t = require('luatest')
local g = t.group('feature')
-- Tests. All properties with name staring with `test` are treated as
test cases.
g.test_example_1 = function() ... end
g.test_example_n = function() ... End
-- Define parametrized groups
local pg = t.group('pgroup', {{engine = 'memtx'}, {engine = 'vinyl'}})
pg.test_example_3 = function(cg)
-- Use cg.params here
box.schema.space.create('test', {
engine = cg.params.engine,
})
end
-- Hooks can be specified for one parameter
pg.before_all({engine = 'memtx'}, function() ... end)
pg.before_each({engine = 'memtx'}, function() ... end)
Tarantool
https://github.com/tarantool/l
uatest/
28.
29.
30. local function sum_up_to(n)
local sum = 0
for i = 1, n do
sum = sum + i
end
return sum
end
property 'sum of numbers is equal to (n + 1) * n / 2' {
generators = { int(100) },
check = function(n)
return sum_up_to(n) == (n + 1) * n / 2
end
}
https://github.com/luc-tielen/lua-quickcheck
31.
32. Lua ---@alias hookmask string
---|+'"c"' # Calls hook when Lua calls a function.
---|+'"r"' # Calls hook when Lua returns from a function.
---|+'"l"' # Calls hook when Lua enters a new line of
code.
---
---Sets the given function as a hook.
---
---[View documents](command:extension.lua.doc?["en-
us/51/manual.html/pdf-debug.sethook"])
---
---@overload fun(hook: function, mask: hookmask, count?:
integer)
---@param thread thread
---@param hook async fun()
---@param mask hookmask
---@param count? Integer
function debug.sethook(thread, hook, mask, count) end
33. Lua ---@param co? thread
---@return function hook
---@return string mask
---@return integer count
---@nodiscard
function debug.gethook(co) end
---@alias infowhat string
---|+'"n"' # `name` and `namewhat`
---|+'"S"' # `source`, `short_src`, `linedefined`,
`lastlinedefined`, and `what`
---|+'"l"' # `currentline`
---|+'"t"' # `istailcall`
---|+'"u"' # `nups`, `nparams`, and `isvararg`
---|+'"f"' # `func`
---|+'"L"' # `activelines`
34. Lua ---
---Returns a table with information about a function.
---
---[View documents](command:extension.lua.doc?["en-
us/51/manual.html/pdf-debug.getinfo"])
---
---@overload fun(f: integer|function, what?:
infowhat):debuginfo
---@param thread thread
---@param f integer|async fun()
---@param what? infowhat
---@return debuginfo
---@nodiscard
function debug.getinfo(thread, f, what) end
35. Lua ---
---Assigns the `value` to the local variable with index
`local` of the function at `level` of the stack.
---
---[View documents](command:extension.lua.doc?["en-
us/51/manual.html/pdf-debug.setlocal"])
---
---@overload fun(level: integer, index: integer, value:
any):string
---@param thread thread
---@param level integer
---@param index integer
---@param value any
---@return string name
function debug.setlocal(thread, level, index, value) end
36. Lua ---
---Returns the name and the value of the local variable
with index `local` of the function at level `f` of the
stack.
---
---[View documents](command:extension.lua.doc?["en-
us/51/manual.html/pdf-debug.getlocal"])
---
---@overload fun(f: integer|async fun(), index:
integer):string, any
---@param thread thread
---@param f integer|async fun()
---@param index integer
---@return string name
---@return any value
---@nodiscard
function debug.getlocal(thread, f, index) end
37. Lua
---
---Returns the name and the value of the upvalue with
index `up` of the function.
---
---[View documents](command:extension.lua.doc?["en-
us/51/manual.html/pdf-debug.getupvalue"])
---
---@param f async fun()
---@param up integer
---@return string name
---@return any value
---@nodiscard
function debug.getupvalue(f, up) end
38. Lua ---
---Returns a string with a traceback of the call stack.
The optional message string is appended at the beginning
of the traceback.
---
---[View documents](command:extension.lua.doc?["en-
us/51/manual.html/pdf-debug.traceback"])
---
---@overload fun(message?: any, level?: integer): string
---@param thread thread
---@param message? any
---@param level? integer
---@return string message
---@nodiscard
function debug.traceback(thread, message, level) end
39. Debug API:
local loadFunct = assert( loadfile(fileToExec) )
debug.sethook(covHandler, "l" )
-- Workaround: coroutines require us to sethook again.
local oldCreate = coroutine.create
coroutine.create = function( coFunct )
return oldCreate( function(...)
debug.sethook(covHandler, "l" )
return coFunct(unpack(arg))
end)
end
loadFunct() -- run the main program
M.stop()
function M.stop()
debug.sethook()
…
end
end
40. Debug API: function traceback ()
local level = 1
while true do
local info = debug.getinfo(level, "Sl")
if not info then break end
if info.what == "C" then -- is a C function?
print(level, "C function")
else -- a Lua function
print(string.format("[%s]:%d",
info.short_src,
info.currentline))
end
level = level + 1
end
end
`n´ selects fields name and namewhat
`f´ selects field func
`S´ selects fields source, short_src,
what, and linedefined
`l´ selects field currentline
`u´ selects field nup
41.
42. • gperftools, callgrind покажут граф
вызовов на уровне Си
• jit.p позволит собрать граф вызовов
в LuaJIT
• Но никто не соберёт всё вместе
43. • Строчный хук позволяет собрать профиль исполнения
• Тайминги будут неверные…
• … но счетчикам строк верить можно
• Luacov - https://github.com/keplerproject/luacov
45. • Luatrace - https://github.com/geoffleyland/luatrace
“luatrace is a Lua module that collects information about
what your code is doing and how long it takes, and can
analyse that information to generate profile and coverage
reports.
luatrace adds a layer on top of Lua's debug hooks to make
it easier to collect information for profiling and coverage
analysis. luatrace traces of every line executed, not just
calls.
luatrace can trace through coroutine resumes and yields,
and through xpcalls, pcalls and errors. On some platforms it
uses high resolution timers to collect times of the order of
nanoseconds.”
49. Lua – язык, с динамической типизацией
Нет проверок времени компиляции
Но можно использовать внешний линтер
для статического анализа кода
50. std = "luajit"
globals = {"box", "_TARANTOOL", "tonumber64"}
ignore = {
-- Accessing an undefined field of a global variable
<debug>.
"143/debug",
-- Accessing an undefined field of a global variable
<string>.
"143/string",
-- Accessing an undefined field of a global variable
<table>.
"143/table",
-- Unused argument <self>.
"212/self",
-- Redefining a local variable.
"411",
-- Redefining an argument.
"412",
-- line contains only whitespace
"611",
}
include_files = {
"**/*.lua",
}
exclude_files = {
"build*/**/*.lua",
-- Third-party source code.
"test-run/**/*.lua",
".rocks/**/*.lua",
".git/**/*.lua",
}
55. require('checks')
local function get_stat(uri, opts)
checks('string', {timeout =
'?number'})
end
get_stat()
-- error: bad argument #1 to get_stat
(string expected, got nil)
get_stat('localhost', {timeuot = 1})
-- error: unexpected argument opts.timeuot
to get_stat
13
get_stat('localhost', {timeuot = 1})
-- ^^ typo
-- No error, but does not work as expected
-- Still bad
https://github.com/tarantool/checks
57. Teal –
local ltn12 = require("ltn12")
local Sink = ltn12.Sink
local Source = ltn12.Source
local record socket
record TCP
-- master methods
bind: function(TCP, string, integer)
connect: function(TCP, string, integer): integer, string
listen: function(TCP, integer): integer, string
-- client methods
getpeername: function(TCP): string, integer
enum TCPReceivePattern
"*l"
"*a"
end
enum TCPReceiveError
"closed"
"timeout"
end
receive: function(TCP, TCPReceivePattern|integer, string):
string, TCPReceiveError
https://github.com/teal-language/teal-
types/blob/master/types/luasocket/socket.d.tl
58.
59. • Нет готового решения с удобным отладчиком
• Есть отладчик в Redis как артефакт поддержки в
ZeroBrane Studio Павла Ключенко
• Есть популярный модуль Mobdebug того же Павла
Ключенко
• Развитие RemDebug;
• Mobdebug можно поднять почти везде, где есть LuaSocket;
• Есть надежды зайти через VSCode Debug Adapter
API
66. Basically Vscode-debuggee.lua drops the speed of running Lua
programs because it implements the breakpoint mechanism
using debug.sethook.
This performance degradation can be overcome by applying a
simple patch to the Lua VM.
Download:
lua 5.1.5 : Patch, Code
lua 5.3.4 : Patch, Code
https://marketplace.visualstudio.com/items?itemName=devCAT.lua-debug