aboutsummaryrefslogtreecommitdiffstats
path: root/tests/fuzzer_spec.lua
blob: c47f1e500d9cb0113ec2663fdcb085614a39f290 (plain)
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
require("plenary.async").tests.add_to_env()
local conform = require("conform")
local log = require("conform.log")
local runner = require("conform.runner")
local test_util = require("tests.test_util")

describe("fuzzer", function()
  before_each(function()
    conform.formatters.test = {
      meta = { url = "", description = "" },
      command = "tests/fake_formatter.sh",
    }
  end)

  after_each(function()
    test_util.reset_editor()
  end)

  ---@param buf_content string[]
  ---@param expected string[]
  ---@param opts? table
  local function run_formatter(buf_content, expected, opts)
    local bufnr = vim.fn.bufadd("testfile")
    vim.fn.bufload(bufnr)
    vim.api.nvim_set_current_buf(bufnr)
    vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, buf_content)
    vim.bo[bufnr].modified = false
    runner.apply_format(0, buf_content, expected, nil, false)
    assert.are.same(expected, vim.api.nvim_buf_get_lines(0, 0, -1, false))
  end

  local function make_word()
    local chars = {}
    for _ = 1, math.random(1, 10) do
      table.insert(chars, string.char(math.random(97, 122)))
    end
    return table.concat(chars, "")
  end

  local function make_line()
    local words = {}
    for _ = 1, math.random(0, 6) do
      table.insert(words, make_word())
    end
    return table.concat(words, " ")
  end

  local function make_file(num_lines)
    local lines = {}
    for _ = 1, math.random(1, num_lines) do
      table.insert(lines, make_line())
    end
    return lines
  end

  local function do_insert(lines)
    local idx = math.random(1, #lines + 1)
    for _ = 1, math.random(1, 3) do
      table.insert(lines, idx, make_line())
    end
  end

  local function do_replace(lines)
    local num_lines = math.random(1, math.min(3, #lines))
    local idx = math.random(1, #lines - num_lines + 1)
    local replacement = {}
    local num_replace = math.random(1, 5)
    for _ = 1, num_replace do
      table.insert(replacement, make_line())
    end
    local col = math.random(1, lines[idx]:len())
    replacement[1] = lines[idx]:sub(1, col) .. replacement[1]
    col = math.random(1, lines[idx + num_lines - 1]:len())
    replacement[#replacement] = replacement[#replacement] .. lines[idx + num_lines - 1]:sub(col)

    for _ = 1, num_lines - num_replace do
      table.remove(lines, idx)
    end
    for _ = 1, num_replace - num_lines do
      table.insert(lines, idx, "")
    end
    for i = 1, num_replace do
      lines[idx + i - 1] = replacement[i]
    end
  end

  local function do_delete(lines)
    local num_lines = math.random(1, 3)
    local idx = math.random(1, #lines - num_lines)
    for _ = 1, num_lines do
      table.remove(lines, idx)
    end
    -- vim will never let the lines be empty. An empty file has a single blank line.
    if #lines == 0 then
      table.insert(lines, "")
    end
  end

  local function make_edits(lines)
    local was_empty = table.concat(lines):match("^%s*$")
    lines = vim.deepcopy(lines)
    for _ = 1, math.random(0, 3) do
      do_insert(lines)
    end
    for _ = 1, math.random(0, 3) do
      do_replace(lines)
    end
    for _ = 1, math.random(0, 3) do
      do_delete(lines)
    end
    -- avoid blank output (whitepsace only) which is ignored when applying formatting
    if not was_empty then
      while table.concat(lines):match("^%s*$") do
        do_replace(lines)
      end
    end
    return lines
  end

  it("formats correctly", function()
    -- log.level = vim.log.levels.TRACE
    for i = 1, 50000 do
      math.randomseed(i)
      log.info("Fuzz testing with seed %d", i)
      local content = make_file(20)
      local formatted = make_edits(content)
      run_formatter(content, formatted)
    end
  end)
end)