aboutsummaryrefslogtreecommitdiff
path: root/test/unit/profile_spec.lua
blob: 08e5cedbabd079596aafa4744eaa7d157c599e3c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
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)