Include: include/setup.vader

Execute (neomake#GetMakers):
  AssertEqual neomake#GetMakers('non-existent'), []
  AssertEqual neomake#GetMakers('pug'), ['puglint']

  let sh_makers = ['checkbashisms', 'dash', 'sh', 'shellcheck']
  AssertEqual sort(neomake#GetMakers('sh')), sh_makers

  Save g:neomake_sh_enabled_makers
  let g:neomake_sh_enabled_makers = ['sh']
  AssertEqual sort(neomake#GetMakers('sh')), sh_makers

Execute (neomake#GetMakers uses makers from buffer vars):
  new
  let b:neomake_ft1_my_custom_maker = {}
  let b:neomake_ft1_my_custom2_maker = {}
  let b:neomake_ft2_my_custom2_maker = {}

  AssertEqual neomake#GetMakers('ft1'), ['my_custom', 'my_custom2']
  AssertEqual neomake#GetMakers('ft2'), ['my_custom2']
  AssertEqual neomake#GetMakers('ft1.ft2'), ['my_custom', 'my_custom2']
  bwipe

Execute (neomake#GetMakers uses makers from new-style config):
  new
  noautocmd set filetype=myft
  let maker1 = {'name': 'maker1'}
  let maker2 = {'name': 'maker2'}
  call neomake#config#set('b:ft.myft.my_custom.maker', maker1)
  call neomake#config#set('b:ft.myft.my_custom2.maker', maker2)

  AssertEqual neomake#GetMaker('my_custom').name, 'maker1'
  AssertNeomakeMessage "Using setting maker={'name': 'maker1'} from 'buffer' (prefix: ['ft', 'myft', 'my_custom']).", 3

  AssertEqual neomake#GetMaker('my_custom2').name, 'maker2'
  AssertNeomakeMessage "Using setting maker={'name': 'maker2'} from 'buffer' (prefix: ['ft', 'myft', 'my_custom2']).", 3

  AssertEqual neomake#GetMakers('myft'), ['my_custom', 'my_custom2']
  AssertNeomakeMessage "Using setting ft.myft={'my_custom': {'maker': {'name': 'maker1'}}, 'my_custom2': {'maker': {'name': 'maker2'}}} from 'buffer'.", 3
  bwipe

Execute (neomake#GetMakers new-style config with old-style):
  new
  noautocmd set filetype=myft

  let maker1 = {'name': 'maker1'}
  call neomake#config#set('b:ft.myft.my_custom.maker', maker1)

  let maker2 = {'name': 'maker2'}
  let b:neomake_myft_my_custom_maker = maker2

  " Maker name is not duplicated.
  AssertEqual neomake#GetMakers('myft'), ['my_custom']

  " GetMaker prefers new-style config.
  AssertEqual neomake#GetMaker('my_custom').name, 'maker1'
  bwipe

Execute (neomake#GetMaker uses defined errorformat with makeprg):
  Save &errorformat
  let &errorformat = '%Gcustom'
  AssertEqual neomake#GetMaker('makeprg', '').errorformat, '%Gcustom'

Execute (neomake#GetMaker defaults to current filetype):
"   Save &errorformat
"   let &errorformat = '%Gcustom'
"   AssertEqual neomake#GetMaker('makeprg', '').errorformat, '%Gcustom'

Execute (neomake#GetMaker uses defaults from b:/g:):
  Save g:neomake_test_remove_invalid_entries
  Save b:neomake_test_remove_invalid_entries

  let maker = {'name': 'test'}

  " Default.
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 0,
  \ "default is ok"

  let maker.remove_invalid_entries = 1
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 1
  let g:neomake_test_remove_invalid_entries = 2
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 2
  let b:neomake_test_remove_invalid_entries = 3
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 3

Execute (neomake#GetMaker uses defaults from b:/g: based on maker):
  Save g:neomake_test_remove_invalid_entries

  let maker = {'name': 'test'}
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 0
  let maker.remove_invalid_entries = 1
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 1
  let g:neomake_test_remove_invalid_entries = 2
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 2
  let b:neomake_test_remove_invalid_entries = 3
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 3

Execute (neomake#GetMaker uses maker_defaults):
  let maker = {'name': 'test'}
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 0

  call neomake#config#set('maker_defaults.remove_invalid_entries', 1)
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 1

  " maker_defaults uses hard-coded defaults when only set partially/loaded.
  AssertEqual neomake#GetMaker(maker).buffer_output, 1

  " maker_defaults uses hard-coded defaults always.
  call neomake#config#set('maker_defaults', {})
  AssertEqual neomake#GetMaker(maker).remove_invalid_entries, 0

Execute (neomake#GetMaker from g:neomake_foo_maker):
  let g:neomake_custom_maker = {
    \ 'exe': 'my-exe'
    \ }
  let maker = neomake#GetMaker('custom')
  AssertEqual maker.exe, 'my-exe'
  AssertEqual maker.name, 'custom'

Execute (neomake#GetMaker without name):
  let maker = neomake#GetMaker({})
  AssertEqual maker.exe, 'unnamed_maker'
  AssertEqual maker.name, 'unnamed_maker'

Execute (Instantiate all default makers):
  " This is meant to catch issues like https://github.com/neomake/neomake/pull/555.
  let glob_args = [fnamemodify(g:vader_file, ':p:h')
                 \ .'/../autoload/neomake/makers/ft/*.vim', 1, 1]
  " NOTE: not using a list to support older Vim (on Travis).
  let ft_makers = split(call('glob', glob_args[0:-2]), '\n')

  " For vim makers' EnabledMakers to use neomake_checks.
  new
  noautocmd edit autoload/neomake/makers/ft/vim.vim

  let errors_by_file = {}
  let file_errors = []
  for maker_file in ft_makers
    let ft = fnamemodify(maker_file, ':t:r')
    if ft ==# 'haskell'
      NeomakeTestsSkip 'Skipping haskell - expensive setup'
      continue
    endif

    let f = 'neomake#makers#ft#'.ft.'#EnabledMakers'
    try
      let enabled_makers = call(f, [])
    catch /^Vim(let):E119:/
      try
        let enabled_makers = call(f, [{'file_mode': 1, 'bufnr': bufnr('%')}])
      catch
        call add(file_errors, 'Error when calling '.f.'(options): '.v:exception)
      endtry
    catch
      call add(file_errors, 'Error when calling '.f.': '.v:exception)
    endtry

    let makers = neomake#GetMakers(ft)

    " Check that all enabled makers are actually available.
    " Skip neomake_tests, which has "nonexisting" there.
    if ft !=# 'neomake_tests'
        for m in enabled_makers
            if index(makers, m) == -1
                call add(file_errors, printf(
                \ 'Enabled maker %s missing in available makers: %s',
                \ string(m), string(makers)))
            endif
        endfor
    endif

    if ft ==# 'rst'
      " Skip sphinx, would throw an error; it is tested separately.
      " TODO: align with clippy maker, which only uses neomake#log#error
      "       (https://github.com/neomake/neomake/issues/1453).
      call filter(makers, "v:val !=# 'sphinx'")
    endif
    for m in makers
      try
        let maker = neomake#GetMaker(m, ft)
      catch
        call add(file_errors, 'Could not load maker '.string(m).' for filetype '.ft.': '.v:exception)
        continue
      endtry

      " Validate maker.
      let error_with_validate_maker = 1
      try
        let issues = neomake#debug#validate_maker(extend(maker, {'exe': 'true'}))
        let error_with_validate_maker = 0
      finally
        if error_with_validate_maker
          call vader#log(printf('Error with: %s!', m))
        endif
      endtry
      if !empty(issues.errors)
        call add(file_errors, 'Maker '.string(m).' for filetype '.ft.' has errors: '.join(issues.errors))
      endif
      if !empty(issues.warnings)
        call add(file_errors, 'Maker '.string(m).' for filetype '.ft.' has warnings: '.join(issues.warnings))
      endif
    endfor

    if !empty(file_errors)
        let errors_by_file[maker_file] = copy(file_errors)
        let file_errors = []
    endif
  endfor
  Assert empty(file_errors)

  let errors = []
  for m in neomake#GetProjectMakers()
    try
      let maker = neomake#GetMaker(m)
    catch
      call add(errors, 'Could not load project maker '.string(m).': '.v:exception)
      continue
    endtry

    " Validate maker.
    let issues = neomake#debug#validate_maker(extend(maker, {'exe': 'true'}))
    if !empty(issues.errors)
      call add(errors, 'Project maker '.string(m).' has errors: '.join(issues.errors))
    endif
    if !empty(issues.warnings)
      call add(errors, 'Project maker '.string(m).' has warnings: '.join(issues.warnings))
    endif
  endfor

  Assert empty(errors), "Errors when loading makers:\n - ".join(errors, "\n - ")
  if !empty(errors_by_file)
    let msg = ''
    for [fname, errors] in items(errors_by_file)
      let msg .= "\n - ".fnamemodify(fname, ':p:.').":\n"
      let msg .= '   - '.join(errors, "\n   - ")
    endfor
    Assert 0, "Errors when loading makers (per file):".msg
  endif
  AssertEqual exists('*neomake#makers#ft#neomake_tests#EnabledMakers'), 1
  bwipe

Execute (neomake#GetEnabledMakers without make_id):
  Save g:neomake_myft_enabled_makers
  let g:neomake_myft_enabled_makers = ['nonexisting_custom']
  let makers = neomake#GetEnabledMakers('myft')
  AssertNeomakeMessage 'Maker not found (for filetype myft): nonexisting_custom.', 0, {}

Execute (Neomake with non-existing maker (configured)):
  Save g:neomake_verbose, g:neomake_myft_enabled_makers
  let g:neomake_verbose = 3
  let g:neomake_myft_enabled_makers = ['nonexisting_custom']

  new
  noautocmd set filetype=myft
  let make_id_before = neomake#GetStatus().last_make_id
  RunNeomake
  let make_id = neomake#GetStatus().last_make_id
  let log_context = {'make_id': make_id, 'bufnr': bufnr('%')}
  if exists('*win_getid')
    AssertNeomakeMessage "Calling Make with options {'ft': 'myft', 'file_mode': 1, 'winid': ".win_getid()."}.", 3, log_context
  else
    AssertNeomakeMessage "Calling Make with options {'ft': 'myft', 'file_mode': 1}.", 3, log_context
  endif
  AssertNeomakeMessage 'Maker not found (for filetype myft): nonexisting_custom.', 0, log_context
  AssertNeomakeMessage 'Nothing to make: no enabled file mode makers (filetype=myft).', 1, log_context
  AssertEqual len(g:neomake_test_messages), 3
  AssertEqual make_id_before + 1, neomake#GetStatus().last_make_id
  bwipe

Execute (Neomake with non-existing default makers):
  call g:NeomakeSetupAutocmdWrappers()
  new
  set filetype=neomake_tests
  Save g:neomake_test_enabledmakers
  let g:neomake_test_enabledmakers = ['maker_without_exe', 'nonexisting']
  RunNeomake
  AssertNeomakeMessage 'Maker not found (for filetype neomake_tests): nonexisting.', 3
  AssertNeomakeMessage "Exe (maker_without_exe) of auto-configured maker maker_without_exe is not executable, skipping.", 3
  AssertNeomakeMessage 'Nothing to make: no valid makers.', 3
  AssertEqual 0, len(g:neomake_test_jobfinished)
  AssertEqual 0, len(g:neomake_test_finished)
  bwipe

Execute (Neomake with non-existing default maker handles following makers):
  call g:NeomakeSetupAutocmdWrappers()
  new
  set filetype=neomake_tests
  Save g:neomake_test_enabledmakers
  let g:neomake_test_enabledmakers = ['maker_without_exe', 'echo_maker']
  RunNeomake
  AssertNeomakeMessage "Exe (maker_without_exe) of auto-configured maker maker_without_exe is not executable, skipping.", 3
  AssertNeomakeMessage 'Running makers: echo_maker (auto).'
  AssertEqual 1, len(g:neomake_test_jobfinished), 'echo_maker should have run'
  AssertEqual 1, len(g:neomake_test_finished)

  RunNeomake
  AssertNeomakeMessage "Exe (maker_without_exe) of auto-configured maker maker_without_exe is not executable, skipping.", 3
  bwipe

Execute (Neomake with non-existing default makers, explicitly called):
  Save &filetype
  set filetype=neomake_tests

  RunNeomake maker_without_exe
  AssertNeomakeMessage "Exe (maker_without_exe) of maker maker_without_exe is not executable.", 0

  RunNeomake maker_without_exe
  AssertNeomakeMessage "Exe (maker_without_exe) of maker maker_without_exe is not executable.", 0

Execute (neomake#GetEnabledMakers with defaults):
  new
  set filetype=neomake_tests
  let makers = neomake#GetEnabledMakers(&ft)
  AssertEqual map(copy(makers), 'v:val.auto_enabled'), [1]
  AssertNeomakeMessage 'Maker not found (for filetype neomake_tests): nonexisting.', 3
  AssertEqual len(g:neomake_test_messages), 1
  bwipe

Execute (neomake#GetEnabledMakers with configuration):
  Save &filetype
  set filetype=neomake_tests

  Save b:neomake_neomake_tests_enabled_makers
  let b:neomake_neomake_tests_enabled_makers = ['maker_without_exe']

  let makers = neomake#GetEnabledMakers(&ft)
  AssertEqual map(copy(makers), 'v:val.auto_enabled'), [0]
  AssertEqual len(g:neomake_test_messages), 0

Execute (neomake#GetEnabledMakers without filetype):
  Save g:neomake_enabled_makers
  unlet! g:neomake_enabled_makers

  AssertEqual map(neomake#GetEnabledMakers(''), 'v:val.name'), []

  AssertEqual map(neomake#GetEnabledMakers(), 'v:val.name'), ['makeprg']

  Save &makeprg
  set makeprg=
  AssertEqual map(neomake#GetEnabledMakers(), 'v:val.name'), []

  Save g:neomake_mymaker_maker
  let g:neomake_mymaker_maker = {}
  let g:neomake_enabled_makers = ['mymaker']
  let makers = neomake#GetEnabledMakers()
  AssertEqual map(copy(makers), '[v:val.name, v:val.auto_enabled]'),
  \ [['mymaker', 0]]

  " Should use a more specific maker based on &filetype.
  set filetype=myft
  let g:neomake_myft_mymaker_maker = {}
  let makers = neomake#GetEnabledMakers()
  AssertEqual map(copy(makers), '[v:val.name, v:val.auto_enabled]'),
  \ [['mymaker', 0]]

  " Should fallback to global maker if not found through filetype.
  unlet g:neomake_myft_mymaker_maker
  let makers = neomake#GetEnabledMakers()
  AssertEqual map(copy(makers), '[v:val.name, v:val.auto_enabled]'),
  \ [['mymaker', 0]]

  AssertEqual len(g:neomake_test_messages), 0

Execute (neomake#GetMaker errors):
  AssertThrows call neomake#GetMaker('cargo', 'foo')
  AssertEqual g:vader_exception, 'Neomake: Maker not found (for filetype foo): cargo'

Execute (neomake#GetMaker for project maker):
  Save g:neomake_enabled_makers
  let g:neomake_enabled_makers = ['cargo']
  set ft=myft

  AssertThrows call neomake#GetMaker('cargo', 'foo')
  AssertEqual g:vader_exception, 'Neomake: Maker not found (for filetype foo): cargo'

Execute (maker.InitForJob can set env var):
  let maker = {
  \ 'exe': 'sh',
  \ 'args': ['-c', 'echo MYENV:$MYENV'],
  \ 'append_file': 0,
  \ 'errorformat': '%m',
  \ 'name': 'echo_env_maker',
  \ }
  function! maker.InitForJob(jobinfo)
    let self.args = ['MYENV=custom', self.exe] + self.args
    let self.exe = 'env'
    let self.errorformat = '%m'
    return self
  endfunction

  let prev_maker = deepcopy(maker)
  call neomake#Make(1, [maker])
  AssertEqual maker, prev_maker
  NeomakeTestsWaitForFinishedJobs
  AssertEqual map(getloclist(0), 'v:val.text'), ['MYENV:custom']

Execute (maker.InitForJob can be a buffer setting (old-style)):
  new
  let maker = {'name': 'mymaker'}

  let s:called = 0
  function! MyInit(jobinfo) dict
    let s:called = 1
    let self.exe = 'doesnotexist'
  endfunction

  let b:neomake_mymaker_InitForJob = function('MyInit')

  CallNeomake 1, [maker]
  AssertEqual s:called, 1
  AssertNeomakeMessage 'Exe (doesnotexist) of maker mymaker is not executable.', 0
  delfunction MyInit
  bwipe

Execute (maker.InitForJob can be a buffer setting (new-style)):
  new
  noautocmd set filetype=neomake_tests
  let maker = {'name': 'mymaker'}

  let s:called = 0
  function! MyInit(jobinfo) dict
    let s:called = 1
    let self.exe = 'doesnotexist'
  endfunction

  call neomake#config#set('b:ft.neomake_tests.InitForJob', function('MyInit'))

  CallNeomake 1, [maker]
  AssertEqual s:called, 1
  AssertNeomakeMessage 'Exe (doesnotexist) of maker mymaker is not executable.', 0
  delfunction MyInit
  bwipe

Execute (Using maker.fn results in deprecation warning):
  let maker = {}
  function! maker.fn(jobinfo) abort
    let self.exe = 'doesnotexist'
  endfunction

  call NeomakeTestsSetVimMessagesMarker()
  CallNeomake 1, [maker]

  AssertNeomakeWarning "Please use 'InitForJob' instead of 'fn' for maker unnamed_maker."
  AssertNeomakeMessage 'Exe (doesnotexist) of maker unnamed_maker is not executable.', 0

Execute (Using maker.args as function results in deprecation warning):
  let maker = {}
  let maker.args = function('getcwd')

  call NeomakeTestsSetVimMessagesMarker()
  CallNeomake 1, [maker]

  AssertNeomakeWarning "Please use 'InitForJob' instead of 'args' for maker unnamed_maker."
  AssertNeomakeMessage 'Exe (unnamed_maker) of maker unnamed_maker is not executable.', 0

Execute (Using maker.args.fn as function results in deprecation warning):
  let maker = {}
  let maker.args = {}
  function! maker.args.fn() abort
    return ['deprecated']
  endfunction

  call NeomakeTestsSetVimMessagesMarker()
  CallNeomake 1, [maker]

  AssertNeomakeWarning "Please use 'InitForJob' instead of 'args.fn' for maker unnamed_maker."
  AssertNeomakeMessage 'Exe (unnamed_maker) of maker unnamed_maker is not executable.', 0

Execute (Set env from maker config):
  Save g:neomake_rust_cargo_maker

  let maker = neomake#GetMaker('cargo', 'rust')
  let maker.args = ['-c', 'env MYENV:$MYENV'] + [maker.exe] + maker.args
  let maker.exe = 'sh'

  let g:neomake_rust_cargo_maker = maker

  let makers = neomake#GetEnabledMakers('rust')
  AssertEqual makers[0].name, 'cargo'
  AssertEqual makers[0], extend(maker, {'auto_enabled': 1})

Execute (Correct function binding from base maker):
  let maker = neomake#GetMaker({'name': 'maker1'})
  AssertEqual maker.exe, 'maker1'
  " Not sure about this one, but it asserts that there is no dict in the string
  " representation of the object's function, when dumping the object.  This was
  " different before, when assigning the function from the base maker dict.
  Assert string(maker) =~# '\v''_get_argv'': function\(''\d+''\),', 'Simple _get_argv'

Execute (Makers: process_output):
  new
  call neomake#statusline#ResetCounts()
  call g:NeomakeSetupAutocmdWrappers()

  Save g:neomake_test_entries
  let g:neomake_test_entries = [{
    \ 'bufnr': bufnr('%'),
    \ 'filename': 'not_used_with_valid_bufnr',
    \ 'lnum': 23,
    \ 'pattern': '',
    \ 'col': 42,
    \ 'vcol': 0,
    \ 'nr': 4711,
    \ 'text': 'error message',
    \ 'type': 'E',
    \ }]

  let maker = {
  \ 'name': 'mymaker',
  \ 'exe': 'printf',
  \ 'args': ['maker_output'],
  \ 'append_file': 0}
  function! maker.process_output(context) abort
    AssertEqual ['maker_output'], a:context.output
    AssertEqual 'stdout', a:context.source
    return deepcopy(g:neomake_test_entries)
  endfunction

  call neomake#Make(1, [maker])
  NeomakeTestsWaitForFinishedJobs

  let expected = copy(g:neomake_test_entries)[0]
  " valid=1 gets added
  let expected.valid = 1
  " filename is removed
  unlet expected.filename
  AssertEqualQf getloclist(0), [expected]

  AssertEqual len(g:neomake_test_countschanged), 1
  AssertEqual len(g:neomake_test_jobfinished), 1
  AssertEqual len(g:neomake_test_finished), 1
  bwipe

Execute (Makers: get_list_entries):
  new
  call neomake#statusline#ResetCounts()
  call g:NeomakeSetupAutocmdWrappers()

  Save g:neomake_test_entries
  let g:neomake_test_entries = [{
    \ 'bufnr': bufnr('%'),
    \ 'filename': 'not_used_with_valid_bufnr',
    \ 'lnum': 23,
    \ 'pattern': '',
    \ 'col': 42,
    \ 'vcol': 0,
    \ 'nr': 4711,
    \ 'text': 'error message',
    \ 'type': 'E',
    \ }]

  let maker = {}
  function! maker.get_list_entries(...) abort dict
    let jobinfo = a:1
    AssertEqual sort(keys(jobinfo.maker)), ['auto_enabled', 'get_list_entries', 'name']
    Assert !has_key(self, 'errorformat'), 'errorformat is not required'
    AssertEqual jobinfo.maker.auto_enabled, 0
    return deepcopy(g:neomake_test_entries)
  endfunction

  let maker = neomake#GetMaker(maker)
  let jobinfos = neomake#Make(1, [maker])
  Assert len(jobinfos), 1

  " valid=1 gets added
  let expected = map(copy(g:neomake_test_entries), "extend(v:val, {'valid': 1})")
  " filename and maker_name is removed
  let expected = map(expected, "filter(v:val, 'v:key != \"filename\"')")
  AssertEqualQf getloclist(0), g:neomake_test_entries

  AssertEqual len(g:neomake_test_countschanged), 1
  AssertEqual len(g:neomake_test_jobfinished), 1
  AssertEqual len(g:neomake_test_finished), 1
  bwipe

Execute (Makers: auto_enabled is kept if provided):
  new
  set filetype=neomake_tests
  let b:neomake_neomake_tests_enabled_makers = [
  \ {'name': 'custom', 'auto_enabled': 1},
  \ ]
  AssertEqual map(neomake#GetEnabledMakers('neomake_tests'), '[v:val.name, v:val.auto_enabled]'),
  \ [['custom', 1]]
  bwipe

Execute (Makers: get_list_entries with non-list return value):
  call neomake#statusline#ResetCounts()
  call g:NeomakeSetupAutocmdWrappers()

  let maker = {}
  function! maker.get_list_entries(...) abort dict
  endfunction

  CallNeomake 1, [maker]
  AssertNeomakeMessage 'unnamed_maker: getting entries via get_list_entries.'
  AssertNeomakeMessage 'The get_list_entries method for maker unnamed_maker did not return a list, but: 0.', 0

  AssertEqual len(g:neomake_test_countschanged), 0
  AssertEqual len(g:neomake_test_jobfinished), 1
  AssertEqual len(g:neomake_test_finished), 1

Execute (Makers: get_list_entries with exception):
  call neomake#statusline#ResetCounts()
  call g:NeomakeSetupAutocmdWrappers()

  let maker = {}
  function! maker.get_list_entries(...) abort dict
    throw "TEST_ERROR"
    return [1]
  endfunction

  CallNeomake 1, [maker]
  AssertNeomakeMessage 'Error during get_list_entries for unnamed_maker: TEST_ERROR.'
  AssertNeomakeMessage '\m^(in function .*)$', 3

  AssertEqual len(g:neomake_test_countschanged), 0
  AssertEqual len(g:neomake_test_jobfinished), 1
  AssertEqual len(g:neomake_test_finished), 1

Execute (Makers: process_output with non-list return value):
  call neomake#statusline#ResetCounts()
  call g:NeomakeSetupAutocmdWrappers()

  let maker = copy(g:success_maker)
  function! maker.process_output(...) abort dict
  endfunction

  CallNeomake 1, [maker]
  AssertNeomakeMessage "Calling maker's process_output method with 1 lines of output on stdout.", 3
  AssertNeomakeMessage 'The process_output method for maker success-maker did not return a list, but: 0.', 0

  AssertEqual len(g:neomake_test_countschanged), 0
  AssertEqual len(g:neomake_test_jobfinished), 1
  AssertEqual len(g:neomake_test_finished), 1

Execute (Makers: process_json with invalid JSON):
  call neomake#statusline#ResetCounts()
  call g:NeomakeSetupAutocmdWrappers()

  let maker = copy(g:success_maker)
  function! maker.process_json(...) abort dict
  endfunction

  CallNeomake 1, [maker]
  if has('nvim')
    AssertNeomakeMessage "Failed to decode JSON: Vim(return):E474: Unidentified byte: success (output: 'success').", 0
  elseif exists('*json_decode')
    AssertNeomakeMessage "Failed to decode JSON: Vim(return):E474: Invalid argument (output: 'success').", 0
  else
    AssertNeomakeMessage "Failed to decode JSON: Failed to parse JSON input: invalid input (output: 'success').", 0
  endif

Execute (Makers: process_json with non-list return value):
  call neomake#statusline#ResetCounts()
  call g:NeomakeSetupAutocmdWrappers()

  let maker = NeomakeTestsCommandMaker('json-maker', 'echo ''{"foo": 1, "bar": 2}''')
  function! maker.process_json(...) abort dict
  endfunction

  CallNeomake 1, [maker]

  AssertNeomakeMessage "Calling maker's process_json method with 2 JSON entries.", 3
  AssertNeomakeMessage 'The process_json method for maker json-maker did not return a list, but: 0.', 0

  function! maker.process_json(...) abort dict
    return [0]
  endfunction
  CallNeomake 1, [maker]
  AssertNeomakeMessage 'The process_json method for maker json-maker did not return a list of dicts, but: [0].', 0

  AssertEqual len(g:neomake_test_countschanged), 0
  AssertEqual len(g:neomake_test_jobfinished), 2
  AssertEqual len(g:neomake_test_finished), 2

Execute (Makers: get_list_entries with sandbox exception):
  call neomake#statusline#ResetCounts()
  call g:NeomakeSetupAutocmdWrappers()

  let maker = {}
  let maker.should_throw = 1
  function! maker.get_list_entries(...) abort dict
    if self.should_throw
      " causes E48
      sandbox bprevious
    endif
    return [1]
  endfunction

  let jobinfo = neomake#Make({'enabled_makers': [maker]})[0]
  AssertNeomakeMessage '\mError during pcall: Vim(bprevious):E48: Not allowed in sandbox:'
  AssertNeomakeMessage '\m^(in function .*)$', 3
  AssertEqual len(g:neomake_test_countschanged), 0
  AssertEqual len(g:neomake_test_jobfinished), 0
  AssertEqual len(g:neomake_test_finished), 0

  let jobinfo.maker.should_throw = 0
  if has('timers')
    AssertNeomakeMessage '\V\^Retrying Timer event in 10ms', 3
  else
    AssertNeomakeMessage 'Retrying Timer event on CursorHold(I).'
    doautocmd CursorHold
  endif
  NeomakeTestsWaitForMessage 'The get_list_entries method for maker unnamed_maker did not return a list of dicts, but: [1].', 0

  AssertEqual len(g:neomake_test_countschanged), 0
  AssertEqual len(g:neomake_test_jobfinished), 1
  AssertEqual len(g:neomake_test_finished), 1

Execute (pcall aborts after 3 attempts per job):
  new
  call g:NeomakeSetupAutocmdWrappers()

  let maker = {'name': 'maker1'}
  function! maker.get_list_entries(...) abort dict
    " causes E48
    sandbox bprevious
  endfunction

  let maker2 = {'_tries': 0, 'name': 'maker2'}
  function! maker2.get_list_entries(...) abort dict
    let self._tries += 1
    if self._tries <= 3
      " causes E48
      sandbox bprevious
    endif
    return [{'bufnr': bufnr('%'), 'text': 'error', 'lnum': 1, 'type': 'E'}]
  endfunction

  call NeomakeTestsSetVimMessagesMarker()
  let jobs = neomake#Make({'enabled_makers': [maker, maker2]})

  " Calls both makers initially.
  AssertNeomakeMessage 'maker1: getting entries via get_list_entries.'
  AssertNeomakeMessage '\mError during pcall: Vim(bprevious):E48: Not allowed in sandbox:', 3, jobs[0]
  AssertNeomakeMessage '\m^(in function .*)$', 3
  if has('timers')
    AssertNeomakeMessage '\V\^Retrying Timer event in 10ms', 3, jobs[0]
  else
    AssertNeomakeMessage 'Retrying Timer event on CursorHold(I).', 3, jobs[0]
  endif
  AssertNeomakeMessage '\mError during pcall: Vim(bprevious):E48: Not allowed in sandbox:', 3, jobs[1]
  if has('timers')
    AssertNeomakeMessage '\V\^Retrying Timer event in 10ms', 3, jobs[1]
  else
    AssertNeomakeMessage 'Retrying Timer event on CursorHold(I).', 3, jobs[1]
    doautocmd CursorHold
  endif

  " Then the first maker is called only.
  NeomakeTestsWaitForMessage '\mError during pcall: Vim(bprevious):E48: Not allowed in sandbox:', 3, jobs[0]
  if has('timers')
    NeomakeTestsWaitForMessage '\V\^Retrying Timer event in 20ms', 3, jobs[0]
  else
    AssertNeomakeMessage 'Retrying Timer event on CursorHold(I).'
    doautocmd CursorHoldI
  endif
  AssertNeomakeMessage 'action queue: skipping handle_get_list_entries for not processed make_id.', 3, jobs[1]

  " Then the second one first (skipped, not re-queued).
  NeomakeTestsWaitForMessage '\mError during pcall: Vim(bprevious):E48: Not allowed in sandbox:', 3, jobs[1]
  if has('timers')
"     NeomakeTestsWaitForMessage '\V\^Retrying Timer event in 30ms', 3, jobs[0]
    NeomakeTestsWaitForMessage 'action queue: skipping handle_get_list_entries for not processed make_id.', 3, jobs[0]
  else
    AssertNeomakeMessage 'Retrying Timer event on CursorHold(I).'
    doautocmd CursorHold
    doautocmd CursorHold
    doautocmd CursorHold
  endif

  NeomakeTestsWaitForMessage 'Giving up handling Timer callbacks after 3 attempts. Please report this. See :messages for more information.', 0, jobs[0]
  AssertNeomakeMessage '\m^(in function .*neomake#action_queue#add,.*)$', 3

  " Now maker2 gets processed (after giving up on maker1).
  if has('timers')
    NeomakeTestsWaitForMessage 'Processing 1 entries.', 3, jobs[1]
  else
    AssertNeomakeMessage 'Processing 1 entries.', 3, jobs[1]
  endif

  AssertEqual len(g:neomake_test_finished), 1
  AssertEqual map(copy(g:neomake_test_jobfinished), 'v:val.jobinfo.maker.name'), ['maker2']
  AssertEqual len(g:neomake_test_countschanged), 1
  AssertEqual map(getloclist(0), 'v:val.text'), ['error']

  let vim_msgs = NeomakeTestsGetVimMessages()
  Assert vim_msgs[-1] =~# '\vNeomake error in: function .*neomake#action_queue#add, line \d+'
  AssertEqual len(vim_msgs), 1
  bwipe

Execute (Makers: get_list_entries via config):
  call g:NeomakeSetupAutocmdWrappers()
  let maker = {'name': 'mymaker', 'exe': 'doesnotexist'}

  Save g:neomake_test_entries
  let g:neomake_test_entries = [{
    \ 'filename': 'unloaded_filename_without_bufnr',
    \ 'lnum': 23,
    \ 'pattern': '',
    \ 'col': 42,
    \ 'vcol': 0,
    \ 'nr': 4711,
    \ 'text': 'error message',
    \ 'type': 'E',
    \ }]

  function! NeomakeTestGetEntries(...) abort dict
    let a = a:000
    Assert !has_key(self, 'errorformat'), 'errorformat is not required'
    return deepcopy(g:neomake_test_entries)
  endfunction

  new
  let bufnr = bufnr('%')
  set filetype=neomake_tests
  let b:neomake_neomake_tests_enabled_makers = [maker]
  let b:neomake_neomake_tests_mymaker_get_list_entries = function('NeomakeTestGetEntries')
  RunNeomake

  " valid=1 gets added
  let expected = map(copy(g:neomake_test_entries), "extend(v:val, {'valid': 1})")
  " filename is removed
  let expected = map(expected, "filter(v:val, 'v:key != \"filename\"')")
  " Unlisted buffer gets added for filename.
  let unlisted_bufnr = bufnr('unloaded_filename_without_bufnr')
  Assert !empty(unlisted_bufnr), 'unlisted_bufnr is not empty'
  let expected[0].bufnr = unlisted_bufnr

  AssertEqualQf getloclist(0), expected

  AssertEqual len(g:neomake_test_countschanged), 1
  AssertEqual len(g:neomake_test_jobfinished), 1
  AssertEqual len(g:neomake_test_finished), 1
  bwipe
  bwipe unloaded_filename_without_bufnr
  delfunction NeomakeTestGetEntries

Execute (neomake#Make can be called with dict):
  call neomake#Make({})
  AssertNeomakeMessage 'Nothing to make: no enabled file mode makers (filetype=).', 1

Execute (neomake#Make uses current filetype by default):
  new
  set ft=neomake_tests
  let b:neomake_neomake_tests_true_append_file = 0
  let s:au_called = []
  augroup neomake_tests
    autocmd User NeomakeJobFinished AssertEqual g:neomake_hook_context.jobinfo.ft, 'neomake_tests'
    autocmd User NeomakeJobFinished call add(s:au_called, 1)
  augroup END
  call neomake#Make({'enabled_makers': ['true']})
  NeomakeTestsWaitForFinishedJobs
  AssertEqual s:au_called, [1]
  bwipe

Execute (Neomake/Neomake! run ft maker in project mode):
  call g:NeomakeSetupAutocmdWrappers()
  new
  edit tests/fixtures/a\ filename\ with\ spaces
  set ft=neomake_tests
  let fname = bufname('%')

  Neomake echo_args
  NeomakeTestsWaitForFinishedJobs
  AssertNeomakeMessage 'Running makers: echo_args.'
  AssertEqual map(getloclist(0), 'v:val.text'), [fname]

  Neomake! echo_args
  NeomakeTestsWaitForFinishedJobs
  AssertNeomakeMessage 'Running makers: echo_args.'
  AssertEqual map(getqflist(), 'v:val.text'), ['']

  call neomake#config#set('b:append_file', 1)
  CallNeomake 0, ['echo_args']
  AssertNeomakeMessage 'Running makers: echo_args.'
  AssertNeomakeMessage "Using setting append_file=1 from 'buffer'.", 3
  AssertEqual map(getqflist(), 'v:val.text'), [fname]

  AssertEqual map(getloclist(0), 'v:val.text'), [fname]

  AssertEqual map(copy(g:neomake_test_jobfinished), 'v:val.jobinfo.file_mode'), [1, 0, 0]
  bwipe

Execute (Maker can pass opts for jobstart/job_start):
  if NeomakeAsyncTestsSetup()
    if !has('nvim') && !(has('patch-8.0.0902') && has('patch-8.0.1832'))
      NeomakeTestsSkip 'Requires patch 8.0.0902/8.0.1832 for Vim.'
    else
      let maker = {'exe': 'sh', 'args': ['-c', 'echo $TERM']}
      if has('nvim')
        let maker.nvim_job_opts = {
        \ 'pty': 1,
        \ 'TERM': 'custom-term',
        \ }
      else
        let maker.vim_job_opts = {
        \ 'pty': 1,
        \ 'env': {'TERM': 'custom-term'},
        \ }
      endif

      CallNeomake 0, [maker]
      AssertEqualQf getqflist(), [
      \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 0, 'vcol': 0, 'nr': -1,
      \  'type': '', 'pattern': '', 'text': 'custom-term'}]
    endif
  endif

Execute (Maker can override output handler via opts for jobstart/job_start):
  if NeomakeAsyncTestsSetup()
    let s:output = []
    function s:on_stdout(job_or_channel, output, ...)
      let lines = has('nvim') ? a:output[:-2] : split(a:output, '\n')
      let s:output += lines
    endfunction

    let maker = {'exe': 'printf', 'args': ['%s\n', 'line1', 'line2']}

    if has('nvim')
      let maker.nvim_job_opts = {
      \ 'on_stdout': function('s:on_stdout'),
      \ }
    else
      let maker.vim_job_opts = {
      \ 'out_cb': function('s:on_stdout'),
      \ }
    endif

    CallNeomake 0, [maker]
    AssertEqual getqflist(), []
    AssertEqual s:output, ['line1', 'line2']
  endif

Execute (Job can be skipped via InitForJob):
  call g:NeomakeSetupAutocmdWrappers()
  new
  let maker = {}
  function! maker.InitForJob(...) abort
    throw 'Neomake: skip_job: some reason'
  endfunction
  CallNeomake 1, [maker]
  AssertEqual len(g:neomake_test_finished), 0
  AssertEqual len(g:neomake_test_jobfinished), 0
  AssertNeomakeMessage 'unnamed_maker: skipping job: some reason.', 3

  " Next job gets executed after skipping through InitForJob.
  let maker2 = {}
  CallNeomake 1, [maker, g:entry_maker]

  AssertNeomakeMessage 'unnamed_maker: skipping job: some reason.', 3
  AssertNeomakeMessage 'entry_maker: getting entries via get_list_entries.', 2
  AssertEqual len(g:neomake_test_finished), 1
  AssertEqual len(g:neomake_test_jobfinished), 1
  bwipe!

Execute (Makers: compare list entry defaults):
  " setloclist: => nr=0
  new
  call setloclist(0, [{}])
  let list1 = getloclist(0)
  AssertEqualQf list1, [
  \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'pattern': '', 'valid': 0,
  \  'vcol': 0, 'nr': 0, 'type': '', 'text': ''}]
  bwipe

  " lgetexpr: => nr=-1 (needs text implicitly)
  new
  let &l:efm = '%m'
  lgetexpr 'hastext'
  let list2 = getloclist(0)
  AssertEqualQf list2, [
  \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'pattern': '', 'valid': 0,
  \  'vcol': 0, 'nr': -1, 'type': '', 'text': 'hastext'}]
  bwipe

  " get_list_entries, without text: => nr=0, type=W (default)
  new
  let maker = {}
  function! maker.get_list_entries(...)
    return [{}]
  endfunction
  CallNeomake 1, [maker]
  let list3 = getloclist(0)
  " AssertEqualQf list3, list1
  AssertEqualQf list3, [
  \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'pattern': '', 'valid': 0,
  \  'vcol': 0, 'nr': 0, 'type': 'W', 'text': ''}]
  bwipe

  " get_list_entries, with text: => nr=-1, type=W (default)
  new
  let maker = {}
  function! maker.get_list_entries(...)
    return [{'text': 'hastext'}]
  endfunction
  CallNeomake 1, [maker]
  let list4 = getloclist(0)
  " AssertEqualQf list4, list2
  AssertEqualQf list4, [
  \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'pattern': '', 'valid': 0,
  \  'vcol': 0, 'nr': -1, 'type': 'W', 'text': 'hastext'}]
  bwipe

  " cmd maker, without text: => nr=-1
  " This is not consistent - due to appending?!
  new
  let cmd_maker = NeomakeTestsGetMakerWithOutput({}, [''])
  CallNeomake 1, [cmd_maker]
  let list5 = getloclist(0)
  AssertEqualQf list5, [
  \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'pattern': '', 'valid': 0,
  \  'vcol': 0, 'nr': -1, 'type': '', 'text': ''}]
  bwipe

  " cmd maker, with text: => nr=-1
  new
  let cmd_maker = NeomakeTestsGetMakerWithOutput({}, ['hastext'])
  CallNeomake 1, [cmd_maker]
  let list6 = getloclist(0)
  AssertEqualQf list6, list2
  bwipe

Execute (neomake#core#instantiate_maker: does not use copy for self):
  let maker = {
  \ 'args': [],
  \ 'init_call_count': 0,
  \ }
  function! maker.InitForJob(...) abort
    let self.init_call_count += 1
    let self.args += ['arg_for_job']
  endfunction

  let bound_maker = neomake#core#instantiate_maker(maker, {}, 0)
  AssertEqual bound_maker.args, ['arg_for_job']
  let bound_maker = neomake#core#instantiate_maker(maker, {}, 0)
  AssertEqual bound_maker.args, ['arg_for_job', 'arg_for_job']
  AssertEqual maker.init_call_count, 2
  AssertEqual bound_maker.init_call_count, 2

  " deepcopy in InitForJob.
  let maker = {
  \ 'args': [],
  \ 'init_call_count': 0,
  \ }
  function! maker.InitForJob(...) abort
    let self.init_call_count += 1
    let maker = deepcopy(self)
    let maker.args += ['arg_for_job']
    return maker
  endfunction
  let bound_maker = neomake#core#instantiate_maker(maker, {}, 0)
  AssertEqual bound_maker.args, ['arg_for_job']
  let bound_maker = neomake#core#instantiate_maker(maker, {}, 0)
  AssertEqual bound_maker.args, ['arg_for_job']
  AssertEqual maker.init_call_count, 2
  AssertEqual bound_maker.init_call_count, 2

  " copy in InitForJob (mutates args).
  let maker = {
  \ 'args': [],
  \ 'init_call_count': 0,
  \ }
  function! maker.InitForJob(...) abort
    let self.init_call_count += 1
    let maker = copy(self)
    let maker.args += ['arg_for_job']
    return maker
  endfunction
  let bound_maker = neomake#core#instantiate_maker(maker, {}, 0)
  AssertEqual bound_maker.args, ['arg_for_job']
  let bound_maker = neomake#core#instantiate_maker(maker, {}, 0)
  AssertEqual bound_maker.args, ['arg_for_job', 'arg_for_job']
  AssertEqual maker.init_call_count, 2
  AssertEqual bound_maker.init_call_count, 2

  " copy in InitForJob (copied args).
  let maker = {
  \ 'args': [],
  \ 'init_call_count': 0,
  \ }
  function! maker.InitForJob(...) abort
    let self.init_call_count += 1
    let maker = copy(self)
    let maker.args = maker.args + ['arg_for_job']
    return maker
  endfunction
  let bound_maker = neomake#core#instantiate_maker(maker, {}, 0)
  AssertEqual bound_maker.args, ['arg_for_job']
  let bound_maker = neomake#core#instantiate_maker(maker, {}, 0)
  AssertEqual bound_maker.args, ['arg_for_job']
  AssertEqual maker.init_call_count, 2
  AssertEqual bound_maker.init_call_count, 2