Before:
  Save g:ale_set_lists_synchronously
  Save g:ale_buffer_info
  Save g:ale_lsp_error_messages
  Save g:ale_set_loclist
  Save g:ale_set_signs
  Save g:ale_set_quickfix
  Save g:ale_set_highlights
  Save g:ale_echo_cursor
  Save g:ale_disable_lsp
  Save g:ale_history_enabled
  Save g:ale_history_log_output

  let g:ale_disable_lsp = 0
  let g:ale_set_lists_synchronously = 1
  let g:ale_buffer_info = {}
  let g:ale_set_loclist = 1
  " Disable features we don't need for these tests.
  let g:ale_set_signs = 0
  let g:ale_set_quickfix = 0
  let g:ale_set_highlights = 0
  let g:ale_echo_cursor = 0
  let g:ale_history_enabled = 1
  let g:ale_history_log_output = 1

  unlet! g:ale_lsp_error_messages
  unlet! b:ale_linters
  unlet! b:ale_disable_lsp

  call ale#linter#Reset()
  call ale#test#SetDirectory('/testplugin/test')
  call setloclist(0, [])

After:
  Restore

  unlet! b:ale_linters

  call setloclist(0, [])
  call ale#test#RestoreDirectory()
  call ale#linter#Reset()
  call ale#lsp_linter#ClearLSPData()

Given foobar(An empty file):
Execute(tsserver syntax error responses should be handled correctly):
  runtime ale_linters/typescript/tsserver.vim

  if has('win32')
    call ale#test#SetFilename('filename,[]^$.ts')
  else
    call ale#test#SetFilename('filename*?,{}[]^$.ts')
  endif

  call ale#engine#InitBufferInfo(bufnr(''))

  if has('win32')
    AssertEqual 'filename,[]^$.ts', expand('%:p:t')
  else
    AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t')
  endif

  " When we get syntax errors and no semantic errors, we should keep the
  " syntax errors.
  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'seq': 0,
  \ 'type': 'event',
  \ 'event': 'syntaxDiag',
  \ 'body': {
  \   'file': expand('%:p'),
  \   'diagnostics':[
  \     {
  \       'start': {
  \         'line':2,
  \         'offset':14,
  \       },
  \       'end': {
  \         'line':2,
  \         'offset':15,
  \       },
  \       'text': ''','' expected.',
  \       "code":1005
  \     },
  \   ],
  \ },
  \})
  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'seq': 0,
  \ 'type': 'event',
  \ 'event': 'semanticDiag',
  \ 'body': {
  \   'file': expand('%:p'),
  \   'diagnostics':[
  \   ],
  \ },
  \})

  AssertEqual
  \ [
  \   {
  \     'lnum': 1,
  \     'bufnr': bufnr(''),
  \     'col': 14,
  \     'vcol': 0,
  \     'nr': 1005,
  \     'type': 'E',
  \     'text': '1005: '','' expected.',
  \     'valid': 1,
  \     'pattern': '',
  \   },
  \ ],
  \ ale#test#GetLoclistWithoutNewerKeys()

  " After we get empty syntax errors, we should clear them.
  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'seq': 0,
  \ 'type': 'event',
  \ 'event': 'syntaxDiag',
  \ 'body': {
  \   'file': expand('%:p'),
  \   'diagnostics':[
  \   ],
  \ },
  \})

  AssertEqual
  \ [
  \ ],
  \ ale#test#GetLoclistWithoutNewerKeys()

  " Syntax errors on the project root should not populate the LocList.
  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'seq': 0,
  \ 'type': 'event',
  \ 'event': 'syntaxDiag',
  \ 'body': {
  \   'file': g:dir,
  \   'diagnostics':[
  \     {
  \       'start': {
  \         'line':2,
  \         'offset':14,
  \       },
  \       'end': {
  \         'line':2,
  \         'offset':15,
  \       },
  \       'text': ''','' expected.',
  \       "code":1005
  \     },
  \   ],
  \ },
  \})

  AssertEqual
  \ [
  \ ],
  \ ale#test#GetLoclistWithoutNewerKeys()

Execute(tsserver semantic error responses should be handled correctly):
  runtime ale_linters/typescript/tsserver.vim

  if has('win32')
    call ale#test#SetFilename('filename,[]^$.ts')
  else
    call ale#test#SetFilename('filename*?,{}[]^$.ts')
  endif

  call ale#engine#InitBufferInfo(bufnr(''))

  if has('win32')
    AssertEqual 'filename,[]^$.ts', expand('%:p:t')
  else
    AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t')
  endif

  " When we get syntax errors and no semantic errors, we should keep the
  " syntax errors.
  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'seq': 0,
  \ 'type': 'event',
  \ 'event': 'syntaxDiag',
  \ 'body': {
  \   'file': expand('%:p'),
  \   'diagnostics':[
  \   ],
  \ },
  \})
  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'seq': 0,
  \ 'type': 'event',
  \ 'event': 'semanticDiag',
  \ 'body': {
  \   'file': expand('%:p'),
  \   'diagnostics':[
  \     {
  \       'start': {
  \         'line':2,
  \         'offset':14,
  \       },
  \       'end': {
  \         'line':2,
  \         'offset':15,
  \       },
  \       'text': 'Some semantic error',
  \       "code":1005
  \     },
  \   ],
  \ },
  \})

  AssertEqual
  \ [
  \   {
  \     'lnum': 1,
  \     'bufnr': bufnr(''),
  \     'col': 14,
  \     'vcol': 0,
  \     'nr': 1005,
  \     'type': 'E',
  \     'text': '1005: Some semantic error',
  \     'valid': 1,
  \     'pattern': '',
  \   },
  \ ],
  \ ale#test#GetLoclistWithoutNewerKeys()

  " After we get empty syntax errors, we should clear them.
  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'seq': 0,
  \ 'type': 'event',
  \ 'event': 'semanticDiag',
  \ 'body': {
  \   'file': expand('%:p'),
  \   'diagnostics':[
  \   ],
  \ },
  \})

  AssertEqual
  \ [
  \ ],
  \ ale#test#GetLoclistWithoutNewerKeys()

  " Semantic errors on the project root should not populate the LocList.
  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'seq': 0,
  \ 'type': 'event',
  \ 'event': 'semanticDiag',
  \ 'body': {
  \   'file': g:dir,
  \   'diagnostics':[
  \     {
  \       'start': {
  \         'line':2,
  \         'offset':14,
  \       },
  \       'end': {
  \         'line':2,
  \         'offset':15,
  \       },
  \       'text': 'Some semantic error',
  \       "code":1005
  \     },
  \   ],
  \ },
  \})

  AssertEqual
  \ [
  \ ],
  \ ale#test#GetLoclistWithoutNewerKeys()

Execute(tsserver errors should mark tsserver no longer active):
  let b:ale_linters = ['tsserver']
  runtime ale_linters/typescript/tsserver.vim
  call ale#test#SetFilename('filename.ts')
  call ale#engine#InitBufferInfo(bufnr(''))

  let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('typescript')
  Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list)

  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'seq': 0,
  \ 'type': 'event',
  \ 'event': 'semanticDiag',
  \ 'body': {
  \   'file': g:dir . '/filename.ts',
  \   'diagnostics':[],
  \ },
  \})

  AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list

Execute(LSP diagnostics responses should be handled correctly):
  let b:ale_linters = ['eclipselsp']
  runtime ale_linters/java/eclipselsp.vim

  if has('win32')
    call ale#test#SetFilename('filename,[]^$.ts')
  else
    call ale#test#SetFilename('filename*?,{}[]^$.java')
  endif

  call ale#engine#InitBufferInfo(bufnr(''))
  call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'eclipselsp', 'aliases': [], 'lsp': 'stdio'}})

  if has('win32')
    AssertEqual 'filename,[]^$.ts', expand('%:p:t')
  else
    AssertEqual 'filename*?,{}[]^$.java', expand('%:p:t')
  endif

  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'jsonrpc':'2.0',
  \ 'method':'textDocument/publishDiagnostics',
  \ 'params': {
  \     'uri': ale#path#ToFileURI(expand('%:p')),
  \     'diagnostics': [
  \        {
  \          'range': {
  \             'start': {
  \               'line': 0,
  \               'character':0
  \             },
  \             'end': {
  \               'line': 0,
  \               'character':0
  \             }
  \          },
  \          'severity': 2,
  \          'code': "",
  \          'source': 'Java',
  \          'message': 'Missing JRE 1-8'
  \       }
  \     ]
  \  }
  \})

  AssertEqual
  \ [
  \   {
  \     'lnum': 1,
  \     'bufnr': bufnr(''),
  \     'col': 1,
  \     'pattern': '',
  \     'valid': 1,
  \     'vcol': 0,
  \     'nr': -1,
  \     'type': 'W',
  \     'text': 'Missing JRE 1-8'
  \    }
  \ ],
  \ ale#test#GetLoclistWithoutNewerKeys()

Execute(LSP diagnostics responses on project root should not populate loclist):
  let b:ale_linters = ['eclipselsp']
  runtime ale_linters/java/eclipselsp.vim
  call ale#test#SetFilename('filename.java')
  call ale#engine#InitBufferInfo(bufnr(''))
  call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'eclipselsp', 'aliases': [], 'lsp': 'stdio'}})

  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'jsonrpc':'2.0',
  \ 'method':'textDocument/publishDiagnostics',
  \ 'params': {
  \     'uri':'file://' . g:dir,
  \     'diagnostics': [
  \        {
  \          'range': {
  \             'start': {
  \               'line': 0,
  \               'character':0
  \             },
  \             'end': {
  \               'line': 0,
  \               'character':0
  \             }
  \          },
  \          'severity': 2,
  \          'code': "",
  \          'source': 'Java',
  \          'message': 'Missing JRE 1-8'
  \       }
  \     ]
  \  }
  \})

  AssertEqual
  \ [
  \ ],
  \ ale#test#GetLoclistWithoutNewerKeys()

Execute(LSP errors should mark linters no longer active):
  let b:ale_linters = ['pylsp']
  runtime ale_linters/python/pylsp.vim
  call ale#test#SetFilename('filename.py')
  call ale#engine#InitBufferInfo(bufnr(''))
  call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'pylsp', 'aliases': [], 'lsp': 'stdio'}})

  let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('python')
  Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list)

  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'method': 'textDocument/publishDiagnostics',
  \ 'params': {
  \   'uri': ale#path#ToFileURI(g:dir . '/filename.py'),
  \   'diagnostics': [],
  \ },
  \})

  AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list

Execute(LSP pull model diagnostic responses should be handled):
  let b:ale_linters = ['eclipselsp']
  runtime ale_linters/java/eclipselsp.vim

  if has('win32')
    call ale#test#SetFilename('filename,[]^$.ts')
  else
    call ale#test#SetFilename('filename*?,{}[]^$.java')
  endif

  call ale#engine#InitBufferInfo(bufnr(''))
  let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('eclipselsp')
  call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'eclipselsp', 'aliases': [], 'lsp': 'stdio'}})
  call ale#lsp_linter#SetDiagnosticURIMap({'347': ale#util#ToURI(expand('%:p'))})

  if has('win32')
    AssertEqual 'filename,[]^$.ts', expand('%:p:t')
  else
    AssertEqual 'filename*?,{}[]^$.java', expand('%:p:t')
  endif

  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'jsonrpc':'2.0',
  \ 'id': 347,
  \ 'result': {
  \   'kind': 'full',
  \   'items': [
  \     {
  \       'range': {
  \         'start': {
  \           'line': 0,
  \           'character':0
  \         },
  \         'end': {
  \           'line': 0,
  \           'character':0
  \         }
  \       },
  \       'severity': 2,
  \       'code': "",
  \       'source': 'Java',
  \       'message': 'Missing JRE 1-8'
  \     }
  \   ]
  \ },
  \})

  AssertEqual
  \ [
  \   {
  \     'lnum': 1,
  \     'bufnr': bufnr(''),
  \     'col': 1,
  \     'pattern': '',
  \     'valid': 1,
  \     'vcol': 0,
  \     'nr': -1,
  \     'type': 'W',
  \     'text': 'Missing JRE 1-8'
  \    }
  \ ],
  \ ale#test#GetLoclistWithoutNewerKeys()
  AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list

Execute(LSP pull model diagnostic responses that are 'unchanged' should be handled):
  let b:ale_linters = ['eclipselsp']
  runtime ale_linters/java/eclipselsp.vim

  if has('win32')
    call ale#test#SetFilename('filename,[]^$.ts')
  else
    call ale#test#SetFilename('filename*?,{}[]^$.java')
  endif

  call ale#engine#InitBufferInfo(bufnr(''))
  let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('eclipselsp')
  let g:ale_buffer_info[bufnr('')].loclist = [
  \ {
  \   'lnum': 1,
  \   'bufnr': bufnr(''),
  \   'col': 1,
  \   'pattern': '',
  \   'valid': 1,
  \   'vcol': 0,
  \   'nr': -1,
  \   'type': 'W',
  \   'text': 'Missing JRE 1-8'
  \ },
  \]

  call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'eclipselsp', 'aliases': [], 'lsp': 'stdio'}})
  call ale#lsp_linter#SetDiagnosticURIMap({'347': ale#util#ToURI(expand('%:p'))})

  if has('win32')
    AssertEqual 'filename,[]^$.ts', expand('%:p:t')
  else
    AssertEqual 'filename*?,{}[]^$.java', expand('%:p:t')
  endif

  call ale#lsp_linter#HandleLSPResponse(1, {
  \ 'jsonrpc':'2.0',
  \ 'id': 347,
  \ 'result': {
  \     'kind': 'unchanged',
  \ },
  \})

  AssertEqual
  \ [
  \   {
  \     'lnum': 1,
  \     'bufnr': bufnr(''),
  \     'col': 1,
  \     'pattern': '',
  \     'valid': 1,
  \     'vcol': 0,
  \     'nr': -1,
  \     'type': 'W',
  \     'text': 'Missing JRE 1-8'
  \    }
  \ ],
  \ g:ale_buffer_info[bufnr('')].loclist
  AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list

Execute(LSP errors should be logged in the history):
  call ale#lsp_linter#SetLSPLinterMap({'347': {'name': 'foobar', 'aliases': [], 'lsp': 'stdio'}})
  call ale#lsp_linter#HandleLSPResponse(347, {
  \ 'jsonrpc': '2.0',
  \ 'error': {
  \   'code': -32602,
  \   'message': 'xyz',
  \   'data': {
  \     'traceback': ['123', '456'],
  \   },
  \ },
  \})

  AssertEqual
  \ {'foobar': ["xyz\n123\n456"]},
  \ get(g:, 'ale_lsp_error_messages', {})
