summaryrefslogtreecommitdiffstatshomepage
path: root/test/functional/core/spellfile_spec.lua
blob: f06eb76ad18765a2f32834091ad838668f5d62ce (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
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
local t = require('test.testutil')
local n = require('test.functional.testnvim')()

local eq = t.eq
local pcall_err = t.pcall_err
local clear = n.clear
local api = n.api
local fn = n.fn
local rmdir = n.rmdir
local write_file = t.write_file
local mkdir = t.mkdir

local testdir = 'Xtest-functional-spell-spellfile.d'

describe('spellfile', function()
  before_each(function()
    clear({ env = { XDG_DATA_HOME = testdir .. '/xdg_data' } })
    rmdir(testdir)
    mkdir(testdir)
    mkdir(testdir .. '/spell')
  end)
  after_each(function()
    rmdir(testdir)
  end)
  --                   ┌ Magic string (#VIMSPELLMAGIC)
  --                   │       ┌ Spell file version (#VIMSPELLVERSION)
  local spellheader = 'VIMspell\050'
  it('errors out when prefcond section is truncated', function()
    api.nvim_set_option_value('runtimepath', testdir, {})
    -- stylua: ignore
    write_file(testdir .. '/spell/en.ascii.spl',
    --                         ┌ Section identifier (#SN_PREFCOND)
    --                         │   ┌ Section flags (#SNF_REQUIRED or zero)
    --                         │   │   ┌ Section length (4 bytes, MSB first)
               spellheader .. '\003\001\000\000\000\003'
    --             ┌ Number of regexes in section (2 bytes, MSB first)
    --             │       ┌ Condition length (1 byte)
    --             │       │   ┌ Condition regex (missing!)
               .. '\000\001\001')
    api.nvim_set_option_value('spelllang', 'en', {})
    t.matches('Vim%(set%):E758: Truncated spell file', pcall_err(n.command, 'set spell'))
  end)
  it('errors out when prefcond regexp contains NUL byte', function()
    api.nvim_set_option_value('runtimepath', testdir, {})
    -- stylua: ignore
    write_file(testdir .. '/spell/en.ascii.spl',
    --                         ┌ Section identifier (#SN_PREFCOND)
    --                         │   ┌ Section flags (#SNF_REQUIRED or zero)
    --                         │   │   ┌ Section length (4 bytes, MSB first)
               spellheader .. '\003\001\000\000\000\008'
    --             ┌ Number of regexes in section (2 bytes, MSB first)
    --             │       ┌ Condition length (1 byte)
    --             │       │   ┌ Condition regex
    --             │       │   │       ┌ End of sections marker
               .. '\000\001\005ab\000cd\255'
    --             ┌ LWORDTREE tree length (4 bytes)
    --             │               ┌ KWORDTREE tree length (4 bytes)
    --             │               │               ┌ PREFIXTREE tree length
               .. '\000\000\000\000\000\000\000\000\000\000\000\000')
    api.nvim_set_option_value('spelllang', 'en', {})
    t.matches('Vim%(set%):E759: Format error in spell file', pcall_err(n.command, 'set spell'))
  end)
  it('errors out when region contains NUL byte', function()
    api.nvim_set_option_value('runtimepath', testdir, {})
    -- stylua: ignore
    write_file(testdir .. '/spell/en.ascii.spl',
    --                         ┌ Section identifier (#SN_REGION)
    --                         │   ┌ Section flags (#SNF_REQUIRED or zero)
    --                         │   │   ┌ Section length (4 bytes, MSB first)
               spellheader .. '\000\001\000\000\000\008'
    --             ┌ Regions  ┌ End of sections marker
               .. '01234\00067\255'
    --             ┌ LWORDTREE tree length (4 bytes)
    --             │               ┌ KWORDTREE tree length (4 bytes)
    --             │               │               ┌ PREFIXTREE tree length
               .. '\000\000\000\000\000\000\000\000\000\000\000\000')
    api.nvim_set_option_value('spelllang', 'en', {})
    t.matches('Vim%(set%):E759: Format error in spell file', pcall_err(n.command, 'set spell'))
  end)
  it('errors out when SAL section contains NUL byte', function()
    api.nvim_set_option_value('runtimepath', testdir, {})
    -- stylua: ignore
    write_file(testdir .. '/spell/en.ascii.spl',
    --                         ┌ Section identifier (#SN_SAL)
    --                         │   ┌ Section flags (#SNF_REQUIRED or zero)
    --                         │   │   ┌ Section length (4 bytes, MSB first)
               spellheader .. '\005\001\000\000\000\008'
    --             ┌ salflags
    --             │   ┌ salcount (2 bytes, MSB first)
    --             │   │       ┌ salfromlen (1 byte)
    --             │   │       │   ┌ Special character
    --             │   │       │   │┌ salfrom (should not contain NUL)
    --             │   │       │   ││   ┌ saltolen
    --             │   │       │   ││   │   ┌ salto
    --             │   │       │   ││   │   │┌ End of sections marker
               .. '\000\000\001\0024\000\0017\255'
    --             ┌ LWORDTREE tree length (4 bytes)
    --             │               ┌ KWORDTREE tree length (4 bytes)
    --             │               │               ┌ PREFIXTREE tree length
               .. '\000\000\000\000\000\000\000\000\000\000\000\000')
    api.nvim_set_option_value('spelllang', 'en', {})
    t.matches('Vim%(set%):E759: Format error in spell file', pcall_err(n.command, 'set spell'))
  end)
  it('errors out when spell header contains NUL bytes', function()
    api.nvim_set_option_value('runtimepath', testdir, {})
    write_file(testdir .. '/spell/en.ascii.spl', spellheader:sub(1, -3) .. '\000\000')
    api.nvim_set_option_value('spelllang', 'en', {})
    t.matches(
      'Vim%(set%):E757: This does not look like a spell file',
      pcall_err(n.command, 'set spell')
    )
  end)

  it('can be set to a relative path', function()
    local fname = testdir .. '/spell/spell.add'
    api.nvim_set_option_value('spellfile', fname, {})
  end)

  it('can be set to an absolute path', function()
    local fname = fn.fnamemodify(testdir .. '/spell/spell.add', ':p')
    api.nvim_set_option_value('spellfile', fname, {})
  end)

  describe('default location', function()
    it("is stdpath('data')/site/spell/en.utf-8.add", function()
      n.command('set spell')
      n.insert('abc')
      n.feed('zg')
      eq(
        t.fix_slashes(fn.stdpath('data') .. '/site/spell/en.utf-8.add'),
        t.fix_slashes(api.nvim_get_option_value('spellfile', {}))
      )
    end)

    it("is not set if stdpath('data') is not writable", function()
      n.command('set spell')
      fn.writefile({ '' }, testdir .. '/xdg_data')
      n.insert('abc')
      eq("Vim(normal):E764: Option 'spellfile' is not set", pcall_err(n.command, 'normal! zg'))
    end)

    it("is not set if 'spelllang' is not set", function()
      n.command('set spell spelllang=')
      n.insert('abc')
      eq("Vim(normal):E764: Option 'spellfile' is not set", pcall_err(n.command, 'normal! zg'))
    end)

    it('accepts overwrites on midword and syllable section, errors on memory leak', function()
      api.nvim_set_option_value('runtimepath', testdir, {})
      -- stylua: ignore

      -- Set SN_MIDWORD and SN_SYLLABLE twice so the loader overwrites lp->sl_midword. Should
      -- not leak memory.
      write_file(testdir .. '/spell/en.ascii.spl',
                 spellheader
    --            ┌ Section identifier (#SN_MIDWORD)
    --            │   ┌ Section flags (#SNF_REQUIRED or zero)
    --            │   │   ┌ Section length (4 bytes, MSB first)
              .. '\002\001\000\000\000\003abc'  -- SN_MIDWORD (midword = "abc")
              .. '\002\001\000\000\000\003def'  -- Overwrites SN_MIDWORD (midword = "def")
    --            ┌ Section identifier (#SN_SYLLABLE)
    --            │   ┌ Section flags (#SNF_REQUIRED or zero)
    --            │   │   ┌ Section length (4 bytes, MSB first)
              .. '\009\001\000\000\000\003ghi'  -- SN_SYLLABLE (syllable = "ghi")
              .. '\009\001\000\000\000\003jib'  -- Overwrites SN_SYLLABLE (syllable = "jib")
              .. '\255'                         -- End of sections marker
              .. '\000\000\000\000'             -- LWORDTREE len
              .. '\000\000\000\000'             -- KWORDTREE len
              .. '\000\000\000\000'             -- PREFIXTREE len
      )

      api.nvim_set_option_value('spelllang', 'en', {})
      n.command('set spell') -- Should not error
      eq(true, api.nvim_get_option_value('spell', {}))
    end)
  end)
end)