local helpers = require('test.unit.helpers')(after_each) local itp = helpers.gen_itp(it) local cimport = helpers.cimport local ffi = helpers.ffi local eq = helpers.eq local neq = helpers.neq local prof = cimport('./src/nvim/profile.h') local function split(inputstr, sep) if sep == nil then sep = "%s" end local t, i = {}, 1 for str in string.gmatch(inputstr, "([^"..sep.."]+)") do t[i] = str i = i + 1 end return t end local function trim(s) local from = s:match"^%s*()" return from > #s and "" or s:match(".*%S", from) end local function starts(str, start) return string.sub(str, 1, string.len(start)) == start end local function cmp_assert(v1, v2, op, opstr) local res = op(v1, v2) if res == false then print(string.format("expected: %f %s %f", v1, opstr, v2)) end assert.is_true(res) end local function lt(a, b) -- luacheck: ignore cmp_assert(a, b, function(x, y) return x < y end, "<") end local function lte(a, b) -- luacheck: ignore cmp_assert(a, b, function(x, y) return x <= y end, "<=") end local function gt(a, b) -- luacheck: ignore cmp_assert(a, b, function(x, y) return x > y end, ">") end local function gte(a, b) cmp_assert(a, b, function(x, y) return x >= y end, ">=") end -- missing functions: -- profile_self -- profile_get_wait -- profile_set_wait -- profile_sub_wait describe('profiling related functions', function() local function profile_start() return prof.profile_start() end local function profile_end(t) return prof.profile_end(t) end local function profile_zero() return prof.profile_zero() end local function profile_setlimit(ms) return prof.profile_setlimit(ms) end local function profile_passed_limit(t) return prof.profile_passed_limit(t) end local function profile_add(t1, t2) return prof.profile_add(t1, t2) end local function profile_sub(t1, t2) return prof.profile_sub(t1, t2) end local function profile_divide(t, cnt) return prof.profile_divide(t, cnt) end local function profile_cmp(t1, t2) return prof.profile_cmp(t1, t2) end local function profile_equal(t1, t2) return prof.profile_equal(t1, t2) end local function profile_msg(t) return ffi.string(prof.profile_msg(t)) end local function toseconds(t) -- luacheck: ignore local str = trim(profile_msg(t)) local spl = split(str, ".") local s, us = spl[1], spl[2] return tonumber(s) + tonumber(us) / 1000000 end describe('profile_equal', function() itp('times are equal to themselves', function() local start = profile_start() assert.is_true(profile_equal(start, start)) local e = profile_end(start) assert.is_true(profile_equal(e, e)) end) itp('times are unequal to others', function() assert.is_false(profile_equal(profile_start(), profile_start())) end) end) -- this is quite difficult to test, as it would rely on other functions in -- the profiling package. Those functions in turn will probably be tested -- using profile_cmp... circular reasoning. describe('profile_cmp', function() itp('can compare subsequent starts', function() local s1, s2 = profile_start(), profile_start() assert.is_true(profile_cmp(s1, s2) > 0) assert.is_true(profile_cmp(s2, s1) < 0) end) itp('can compare the zero element', function() assert.is_true(profile_cmp(profile_zero(), profile_zero()) == 0) end) itp('correctly orders divisions', function() local start = profile_start() assert.is_true(profile_cmp(start, profile_divide(start, 10)) <= 0) end) end) describe('profile_divide', function() itp('actually performs division', function() -- note: the routine actually performs floating-point division to get -- better rounding behaviour, we have to take that into account when -- checking. (check range, not exact number). local divisor = 10 local start = profile_start() local divided = profile_divide(start, divisor) local res = divided for _ = 1, divisor - 1 do res = profile_add(res, divided) end -- res should be in the range [start - divisor, start + divisor] local start_min, start_max = profile_sub(start, divisor), profile_add(start, divisor) assert.is_true(profile_cmp(start_min, res) >= 0) assert.is_true(profile_cmp(start_max, res) <= 0) end) end) describe('profile_zero', function() itp('returns the same value on each call', function() eq(0, profile_zero()) assert.is_true(profile_equal(profile_zero(), profile_zero())) end) end) describe('profile_start', function() itp('increases', function() local last = profile_start() for _ = 1, 100 do local curr = profile_start() gte(curr, last) last = curr end end) end) describe('profile_end', function() itp('the elapsed time cannot be zero', function() neq(profile_zero(), profile_end(profile_start())) end) itp('outer elapsed >= inner elapsed', function() for _ = 1, 100 do local start_outer = profile_start() local start_inner = profile_start() local elapsed_inner = profile_end(start_inner) local elapsed_outer = profile_end(start_outer) gte(elapsed_outer, elapsed_inner) end end) end) describe('profile_setlimit', function() itp('sets no limit when 0 is passed', function() eq(true, profile_equal(profile_setlimit(0), profile_zero())) end) itp('sets a limit in the future otherwise', function() local future = profile_setlimit(1000) local now = profile_start() assert.is_true(profile_cmp(future, now) < 0) end) end) describe('profile_passed_limit', function() itp('start is in the past', function() local start = profile_start() eq(true, profile_passed_limit(start)) end) itp('start + start is in the future', function() local start = profile_start() local future = profile_add(start, start) eq(false, profile_passed_limit(future)) end) end) describe('profile_msg', function() itp('prints the zero time as 0.00000', function() local str = trim(profile_msg(profile_zero())) eq(str, "0.000000") end) itp('prints the time passed, in seconds.microsends', function() local start = profile_start() local endt = profile_end(start) local str = trim(profile_msg(endt)) local spl = split(str, ".") -- string has two parts (before dot and after dot) eq(2, #spl) local s, us = spl[1], spl[2] -- zero seconds have passed (if this is not true, either LuaJIT is too -- slow or the profiling functions are too slow and need to be fixed) eq(s, "0") -- more or less the same goes for the microsecond part, if it doesn't -- start with 0, it's too slow. assert.is_true(starts(us, "0")) end) end) describe('profile_add', function() itp('adds profiling times', function() local start = profile_start() assert.equals(start, profile_add(profile_zero(), start)) end) end) describe('profile_sub', function() itp('subtracts profiling times', function() -- subtracting zero does nothing local start = profile_start() assert.equals(start, profile_sub(start, profile_zero())) local start1, start2, start3 = profile_start(), profile_start(), profile_start() local cmp = profile_cmp(profile_sub(start2, start1), profile_sub(start3, start1)) -- t2 >= t1 => profile_cmp(t1, t2) >= 0 assert.is_true(cmp >= 0) cmp = profile_cmp(profile_sub(start3, start1), profile_sub(start2, start1)) -- t2 <= t1 => profile_cmp(t1, t2) <= 0 assert.is_true(cmp <= 0) end) end) end)