LCOV - code coverage report
Current view: top level - e2e - sample.lua (source / functions) Coverage Total Hit
Test: cluacov tick-mode branch coverage E2E Lines: 38.5 % 273 105
Test Date: 2026-06-25 15:06:17 Functions: - 0 0
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 8.7 % 150 13

             Branch data     Line data    Source code
       1                 :           1 : local M = {}
       2                 :             : 
       3                 :           1 : function M.classify(n)
       4         [ +  + ]:         500 :    if n > 0 then
       5                 :         167 :       return "positive"
       6         [ +  + ]:         333 :    elseif n == 0 then
       7                 :         167 :       return "zero"
       8                 :             :    else
       9                 :         166 :       return "negative"
      10                 :             :    end
      11                 :           1 : end
      12                 :             : 
      13                 :           1 : function M.abs(x)
      14         [ +  # ]:         500 :    if x < 0 then
      15                 :         500 :       return -x
      16                 :             :    end
      17                 :           0 :    return x
      18                 :           1 : end
      19                 :             : 
      20                 :           1 : function M.clamp(x, lo, hi)
      21         [ #  # ]:           0 :    if x < lo then
      22                 :           0 :       return lo
      23         [ #  # ]:           0 :    elseif x > hi then
      24                 :           0 :       return hi
      25                 :             :    end
      26                 :           0 :    return x
      27                 :           1 : end
      28                 :             : 
      29                 :           1 : function M.sum(t)
      30                 :         500 :    local total = 0
      31 [ +  + ][ +  + ]:        2500 :    for i = 1, #t do
      32                 :        2500 :       total = total + t[i]
      33                 :             :    end
      34                 :         500 :    return total
      35                 :           1 : end
      36                 :             : 
      37                 :           1 : function M.find(t, value)
      38         [ +  # ]:         500 :    for _, v in ipairs(t) do
      39         [ +  + ]:        1000 :       if v == value then
      40                 :         500 :          return true
      41                 :             :       end
      42                 :           0 :    end
      43                 :           0 :    return false
      44                 :           1 : end
      45                 :             : 
      46                 :           1 : function M.safe_div(a, b)
      47         [ #  # ]:           0 :    if b == 0 then
      48                 :           0 :       return nil, "division by zero"
      49                 :             :    end
      50                 :           0 :    return a / b
      51                 :           1 : end
      52                 :             : 
      53                 :           1 : function M.fizzbuzz(n)
      54         [ +  # ]:          33 :    if n % 15 == 0 then
      55                 :          33 :       return "fizzbuzz"
      56         [ #  # ]:           0 :    elseif n % 3 == 0 then
      57                 :           0 :       return "fizz"
      58         [ #  # ]:           0 :    elseif n % 5 == 0 then
      59                 :           0 :       return "buzz"
      60                 :             :    else
      61                 :           0 :       return tostring(n)
      62                 :             :    end
      63                 :           1 : end
      64                 :             : 
      65                 :           1 : function M.max_of_three(a, b, c)
      66 [ #  # ][ #  # ]:           0 :    if a >= b and a >= c then
      67                 :           0 :       return a
      68         [ #  # ]:           0 :    elseif b >= c then
      69                 :           0 :       return b
      70                 :             :    else
      71                 :           0 :       return c
      72                 :             :    end
      73                 :           1 : end
      74                 :             : 
      75                 :           1 : function M.any_truthy(a, b, c)
      76 [ #  # ][ #  # ]:           0 :    if a or b or c then
                 [ #  # ]
      77                 :           0 :       return "yes"
      78                 :             :    else
      79                 :           0 :       return "no"
      80                 :             :    end
      81                 :           1 : end
      82                 :             : 
      83                 :           1 : function M.all_truthy(a, b, c)
      84 [ #  # ][ #  # ]:           0 :    if a and b and c then
                 [ #  # ]
      85                 :           0 :       return "all"
      86                 :             :    else
      87                 :           0 :       return "not all"
      88                 :             :    end
      89                 :           1 : end
      90                 :             : 
      91                 :           1 : function M.mixed_logic(x, y)
      92 [ #  # ][ #  # ]:           0 :    if (x > 0 and y > 0) or (x < -10) then
                 [ #  # ]
      93                 :           0 :       return "match"
      94                 :             :    else
      95                 :           0 :       return "no match"
      96                 :             :    end
      97                 :           1 : end
      98                 :             : 
      99                 :             : -- Regression target: function-body first line must be HIT.
     100                 :             : -- Before the savedpc-off-by-one fix in collect_line_hits_recursive,
     101                 :             : -- the very first executable line of a function body (`local t = ...`
     102                 :             : -- right after the function header) was reported with hits = 0,
     103                 :             : -- because the hits-table key (savedpc - code) is the NEXT instruction's
     104                 :             : -- PC and the line aggregator was reading it without the -1 shift.
     105                 :           1 : function M.first_line_local(cobj)
     106                 :           0 :    local t = cobj.kind            -- regression: function-body first line
     107         [ #  # ]:           0 :    if t == "ok" then
     108                 :           0 :       return "ok:" .. t
     109                 :             :    end
     110                 :           0 :    return "skip"
     111                 :           1 : end
     112                 :             : 
     113                 :             : -- Regression target: first executable line INSIDE an if-block.
     114                 :             : -- Same root cause as above. Before the fix this `local cleaned = v` line
     115                 :             : -- consistently reported 0, even though the if-block was clearly entered
     116                 :             : -- (the next line, `out[#out+1] = cleaned`, absorbed the missing hits).
     117                 :             : -- Mirrors the real-world pattern from Path.join_path that triggered
     118                 :             : -- the original bug report.
     119                 :           1 : function M.if_block_first_line(items)
     120                 :           0 :    local out = {}
     121         [ #  # ]:           0 :    for _, v in ipairs(items) do
     122         [ #  # ]:           0 :       if type(v) == "string" then
     123                 :           0 :          local cleaned = v        -- regression: if-block first line
     124                 :           0 :          out[#out + 1] = cleaned
     125                 :             :       end
     126                 :           0 :    end
     127                 :           0 :    return out
     128                 :           1 : end
     129                 :             : 
     130                 :             : -- ===========================================================================
     131                 :             : -- Lua-language showcase block (line 132+).
     132                 :             : --
     133                 :             : -- Everything ABOVE this line is locked: the e2e suite asserts on hard-coded
     134                 :             : -- line numbers (L30, L76, L84, L106, L108, L123). DO NOT shuffle those.
     135                 :             : --
     136                 :             : -- Everything BELOW intentionally exercises a wide variety of Lua control
     137                 :             : -- flow / syntax to make the public coverage demo richer:
     138                 :             : --
     139                 :             : --   * while / break / repeat-until
     140                 :             : --   * numeric-for with explicit step (incl. negative)
     141                 :             : --   * generic-for with pairs / ipairs early-return
     142                 :             : --   * deeply nested if / elseif chains
     143                 :             : --   * short-circuit `or` / `and` for default values & ternary
     144                 :             : --   * pcall / xpcall error capture branches
     145                 :             : --   * goto / continue idiom (Lua 5.2+)
     146                 :             : --   * vararg `...` + select
     147                 :             : --   * multiple return values + destructuring
     148                 :             : --   * closures with upvalues, factory pattern
     149                 :             : --   * direct recursion + mutual recursion
     150                 :             : --   * metatable __index / __call dispatch
     151                 :             : --   * string-keyed dispatch table
     152                 :             : --   * method-call colon syntax
     153                 :             : --
     154                 :             : -- run_test.lua deliberately exercises ONLY a subset of the paths so the
     155                 :             : -- public HTML report shows a believable mix of red & green lines, which
     156                 :             : -- is exactly what someone evaluating cluacov wants to see.
     157                 :             : -- ===========================================================================
     158                 :             : 
     159                 :             : -- while + break: walks t looking for `target`, returns the 1-based index
     160                 :             : -- (or nil if absent). The break path is only taken when target is found
     161                 :             : -- in the middle of the table.
     162                 :           1 : function M.while_break(t, target)
     163                 :           0 :    local i = 1
     164         [ #  # ]:           0 :    while i <= #t do
     165         [ #  # ]:           0 :       if t[i] == target then
     166                 :           0 :          break
     167                 :             :       end
     168                 :           0 :       i = i + 1
     169                 :             :    end
     170         [ #  # ]:           0 :    if i > #t then
     171                 :           0 :       return nil
     172                 :             :    end
     173                 :           0 :    return i
     174                 :           1 : end
     175                 :             : 
     176                 :             : -- repeat..until: collects squares until the running total exceeds limit.
     177                 :             : -- The until-condition is evaluated AFTER the body runs at least once.
     178                 :           1 : function M.repeat_until(limit)
     179                 :           0 :    local i = 0
     180                 :           0 :    local total = 0
     181                 :             :    repeat
     182                 :           0 :       i = i + 1
     183                 :           0 :       total = total + i * i
     184         [ #  # ]:           0 :    until total >= limit
     185                 :           0 :    return i, total
     186                 :           1 : end
     187                 :             : 
     188                 :             : -- numeric for with explicit (and possibly negative) step. Three branches
     189                 :             : -- on the step sign let cluacov demonstrate "branch not taken" rendering
     190                 :             : -- when the caller never exercises a particular step direction.
     191                 :           1 : function M.for_step(start_n, stop_n, step)
     192                 :           0 :    local out = {}
     193         [ #  # ]:           0 :    if step == 0 then
     194                 :           0 :       return nil, "step cannot be zero"
     195                 :             :    end
     196 [ #  # ][ #  # ]:           0 :    for i = start_n, stop_n, step do
     197                 :           0 :       out[#out + 1] = i
     198                 :             :    end
     199                 :           0 :    return out
     200                 :           1 : end
     201                 :             : 
     202                 :             : -- generic-for with pairs + early return. `pairs` ordering is undefined,
     203                 :             : -- but the early-return branch is deterministic when key is present.
     204                 :           1 : function M.find_in_map(map, key)
     205         [ #  # ]:           0 :    for k, v in pairs(map) do
     206         [ #  # ]:           0 :       if k == key then
     207                 :           0 :          return v, true
     208                 :             :       end
     209                 :           0 :    end
     210                 :           0 :    return nil, false
     211                 :           1 : end
     212                 :             : 
     213                 :             : -- Deeply nested if/elseif (HTTP status code classifier). Designed to
     214                 :             : -- have many branches; run_test.lua only covers some of them.
     215                 :           1 : function M.classify_status(code)
     216         [ #  # ]:           0 :    if code >= 500 then
     217         [ #  # ]:           0 :       if code == 503 then
     218                 :           0 :          return "unavailable"
     219         [ #  # ]:           0 :       elseif code == 504 then
     220                 :           0 :          return "gateway timeout"
     221                 :             :       else
     222                 :           0 :          return "server error"
     223                 :           0 :       end
     224         [ #  # ]:           0 :    elseif code >= 400 then
     225         [ #  # ]:           0 :       if code == 404 then
     226                 :           0 :          return "not found"
     227 [ #  # ][ #  # ]:           0 :       elseif code == 401 or code == 403 then
     228                 :           0 :          return "auth error"
     229                 :             :       else
     230                 :           0 :          return "client error"
     231                 :           0 :       end
     232         [ #  # ]:           0 :    elseif code >= 300 then
     233                 :           0 :       return "redirect"
     234         [ #  # ]:           0 :    elseif code >= 200 then
     235                 :           0 :       return "ok"
     236                 :             :    else
     237                 :           0 :       return "informational"
     238                 :             :    end
     239                 :           1 : end
     240                 :             : 
     241                 :             : -- Short-circuit `or` for default values. The default branch (a == nil)
     242                 :             : -- is only taken when caller passes nil.
     243                 :           1 : function M.with_default(a, default)
     244         [ #  # ]:           0 :    local v = a or default
     245                 :           0 :    return v
     246                 :           1 : end
     247                 :             : 
     248                 :             : -- Lua's idiomatic ternary: `cond and then_value or else_value`.
     249                 :             : -- The pitfall (then_value being false/nil) is intentionally NOT guarded
     250                 :             : -- here so cluacov shows both branch arms.
     251                 :           1 : function M.ternary_max(a, b)
     252 [ #  # ][ #  # ]:           0 :    return a > b and a or b
     253                 :           1 : end
     254                 :             : 
     255                 :             : -- pcall: protected call with branch on success vs. failure.
     256                 :           1 : function M.try_parse_int(s)
     257                 :           0 :    local ok, result = pcall(function()
     258                 :           0 :       local n = tonumber(s)
     259         [ #  # ]:           0 :       if n == nil then
     260                 :           0 :          error("not a number: " .. tostring(s))
     261                 :             :       end
     262         [ #  # ]:           0 :       if n ~= math.floor(n) then
     263                 :           0 :          error("not an integer: " .. tostring(n))
     264                 :             :       end
     265                 :           0 :       return n
     266                 :           0 :    end)
     267         [ #  # ]:           0 :    if ok then
     268                 :           0 :       return result, nil
     269                 :             :    else
     270                 :           0 :       return nil, result   -- result is the error message
     271                 :             :    end
     272                 :           1 : end
     273                 :             : 
     274                 :             : -- goto/continue idiom (Lua 5.2+). Skips even numbers in the source list.
     275                 :           1 : function M.sum_odd(t)
     276                 :           0 :    local total = 0
     277         [ #  # ]:           0 :    for _, v in ipairs(t) do
     278         [ #  # ]:           0 :       if v % 2 == 0 then
     279                 :           0 :          goto continue
     280                 :             :       end
     281                 :           0 :       total = total + v
     282                 :             :       ::continue::
     283                 :           0 :    end
     284                 :           0 :    return total
     285                 :           1 : end
     286                 :             : 
     287                 :             : -- Vararg + select: count how many args are non-nil.
     288                 :           1 : function M.count_args(...)
     289                 :           0 :    local n = select("#", ...)
     290                 :           0 :    local count = 0
     291 [ #  # ][ #  # ]:           0 :    for i = 1, n do
     292         [ #  # ]:           0 :       if select(i, ...) ~= nil then
     293                 :           0 :          count = count + 1
     294                 :             :       end
     295                 :             :    end
     296                 :           0 :    return count
     297                 :           1 : end
     298                 :             : 
     299                 :             : -- Multiple return values, exercised via destructuring at the call site.
     300                 :           1 : function M.divmod(a, b)
     301         [ #  # ]:           0 :    if b == 0 then
     302                 :           0 :       return nil, nil, "division by zero"
     303                 :             :    end
     304                 :           0 :    local q = math.floor(a / b)
     305                 :           0 :    local r = a - q * b
     306                 :           0 :    return q, r
     307                 :           1 : end
     308                 :             : 
     309                 :             : -- Closure factory: returns a counter closure capturing `count` as upvalue.
     310                 :             : -- Each invocation returns a fresh closure with its own state.
     311                 :           1 : function M.make_counter(start_n)
     312         [ #  # ]:           0 :    local count = start_n or 0
     313                 :             :    return function(step)
     314         [ #  # ]:           0 :       step = step or 1
     315                 :           0 :       count = count + step
     316                 :           0 :       return count
     317                 :           0 :    end
     318                 :           1 : end
     319                 :             : 
     320                 :             : -- Direct recursion + base case. The base case is naturally taken once
     321                 :             : -- per top-level call; the recursive case is taken for any n >= 2.
     322                 :           1 : function M.factorial(n)
     323         [ #  # ]:           0 :    if n <= 1 then
     324                 :           0 :       return 1
     325                 :             :    end
     326                 :           0 :    return n * M.factorial(n - 1)
     327                 :           1 : end
     328                 :             : 
     329                 :             : -- Mutual recursion (even/odd). Forward declaration via local.
     330                 :           1 : local is_even, is_odd
     331                 :           1 : function is_even(n)
     332         [ #  # ]:           0 :    if n == 0 then return true end
     333                 :           0 :    return is_odd(n - 1)
     334                 :           1 : end
     335                 :           1 : function is_odd(n)
     336         [ #  # ]:           0 :    if n == 0 then return false end
     337                 :           0 :    return is_even(n - 1)
     338                 :           1 : end
     339                 :           1 : M.is_even = is_even
     340                 :           1 : M.is_odd  = is_odd
     341                 :             : 
     342                 :             : -- String-keyed dispatch table: typical alternative to long if/elseif.
     343                 :             : -- The fallback branch (unknown verb) is only hit when caller passes an
     344                 :             : -- unknown key.
     345                 :           1 : local dispatch = {
     346                 :           1 :    add = function(a, b) return a + b end,
     347                 :           1 :    sub = function(a, b) return a - b end,
     348                 :           1 :    mul = function(a, b) return a * b end,
     349                 :             :    div = function(a, b)
     350         [ #  # ]:           0 :       if b == 0 then return nil, "div by zero" end
     351                 :           0 :       return a / b
     352                 :           1 :    end,
     353                 :             : }
     354                 :           1 : function M.dispatch(op, a, b)
     355                 :           0 :    local fn = dispatch[op]
     356         [ #  # ]:           0 :    if not fn then
     357                 :           0 :       return nil, "unknown op: " .. tostring(op)
     358                 :             :    end
     359                 :           0 :    return fn(a, b)
     360                 :           1 : end
     361                 :             : 
     362                 :             : -- Metatable __index + colon-syntax method call. The `Point` "class"
     363                 :             : -- mixes both data fields and bound methods.
     364                 :           1 : local Point = {}
     365                 :           1 : Point.__index = Point
     366                 :           1 : function Point.new(x, y)
     367                 :           0 :    return setmetatable({ x = x, y = y }, Point)
     368                 :           1 : end
     369                 :           1 : function Point:translate(dx, dy)
     370                 :           0 :    self.x = self.x + dx
     371                 :           0 :    self.y = self.y + dy
     372                 :           0 :    return self
     373                 :           1 : end
     374                 :           1 : function Point:length()
     375                 :           0 :    return math.sqrt(self.x * self.x + self.y * self.y)
     376                 :           1 : end
     377                 :           1 : M.Point = Point
     378                 :             : 
     379                 :             : -- table.concat with separator + conditional empty branch.
     380                 :           1 : function M.join(t, sep)
     381         [ #  # ]:           0 :    if #t == 0 then
     382                 :           0 :       return ""
     383                 :             :    end
     384         [ #  # ]:           0 :    return table.concat(t, sep or ",")
     385                 :           1 : end
     386                 :             : 
     387                 :             : -- goto/continue: accumulates only non-negative values.
     388                 :             : -- Both branches of `if v < 0` are exercisable:
     389                 :             : --   taken    → goto skip (negative input)
     390                 :             : --   not taken → total = total + v (non-negative input)
     391                 :           1 : function M.goto_filter(t)
     392                 :           0 :    local total = 0
     393         [ #  # ]:           0 :    for _, v in ipairs(t) do
     394         [ #  # ]:           0 :       if v < 0 then
     395                 :           0 :          goto skip
     396                 :             :       end
     397                 :           0 :       total = total + v
     398                 :             :       ::skip::
     399                 :           0 :    end
     400                 :           0 :    return total
     401                 :           1 : end
     402                 :             : 
     403                 :             : -- goto with multiple forward labels: returns the first positive value,
     404                 :             : -- or -1 if none found.
     405                 :             : --   if v > 0  → goto found  (early exit with value)
     406                 :             : --   end of loop → goto done (fall-through, no positive found)
     407                 :           1 : function M.goto_first_match(t)
     408                 :           0 :    local result = -1
     409         [ #  # ]:           0 :    for _, v in ipairs(t) do
     410         [ #  # ]:           0 :       if v > 0 then
     411                 :           0 :          result = v
     412                 :           0 :          goto found
     413                 :             :       end
     414                 :           0 :    end
     415                 :           0 :    goto done
     416                 :             :    ::found::
     417                 :             :    ::done::
     418                 :           0 :    return result
     419                 :           1 : end
     420                 :             : 
     421                 :             : -- goto uncovered: never called from run_test.lua, so the conditional
     422                 :             : -- goto branch stays fully uncovered in the report.
     423                 :           1 : function M.goto_early_return(err)
     424                 :           0 :    local result
     425         [ #  # ]:           0 :    if err then
     426                 :           0 :       goto bail
     427                 :             :    end
     428                 :           0 :    result = "ok"
     429                 :             :    ::bail::
     430         [ #  # ]:           0 :    if result then return result end
     431                 :           0 :    return nil, err
     432                 :           1 : end
     433                 :             : 
     434                 :             : -- assert: exercises assert branches
     435                 :           1 : function M.verify_assert(val)
     436         [ #  # ]:           0 :    assert(val, "value must be truthy")
     437                 :           0 :    return "ok"
     438                 :           1 : end
     439                 :             : 
     440                 :             : -- assert: exercises partial assert branches (always true)
     441                 :           1 : function M.verify_partial_assert(val)
     442         [ #  # ]:           0 :    assert(val, "always true in test")
     443                 :           0 :    return "partial-ok"
     444                 :           1 : end
     445                 :             : 
     446                 :           1 : return M
        

Generated by: LCOV version 2.0-1