1# Copyright (C) 2011-2020 ycmd contributors
2#
3# This file is part of ycmd.
4#
5# ycmd is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# ycmd is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with ycmd.  If not, see <http://www.gnu.org/licenses/>.
17
18from unittest.mock import patch
19import psutil
20
21from hamcrest import ( assert_that,
22                       contains_exactly,
23                       empty,
24                       has_entries,
25                       has_entry,
26                       has_item )
27
28from ycmd import handlers, utils
29from ycmd.tests.clangd import ( IsolatedYcmd, PathToTestFile,
30                                RunAfterInitialized )
31from ycmd.tests.test_utils import ( BuildRequest,
32                                    CompleterProjectDirectoryMatcher,
33                                    MockProcessTerminationTimingOut,
34                                    StopCompleterServer,
35                                    WaitUntilCompleterServerReady )
36
37
38def GetDebugInfo( app ):
39  request_data = BuildRequest( filetype = 'cpp' )
40  return app.post_json( '/debug_info', request_data ).json
41
42
43def GetPid( app ):
44  return GetDebugInfo( app )[ 'completer' ][ 'servers' ][ 0 ][ 'pid' ]
45
46
47def StartClangd( app, filepath = PathToTestFile( 'basic.cpp' ) ):
48  request_data = BuildRequest( filepath = filepath,
49                               filetype = 'cpp' )
50  test = { 'request': request_data }
51  RunAfterInitialized( app, test )
52
53
54def CheckStopped( app ):
55  assert_that(
56    GetDebugInfo( app ),
57    has_entry( 'completer', has_entries( {
58      'name': 'C-family',
59      'servers': contains_exactly( has_entries( {
60        'name': 'Clangd',
61        'pid': None,
62        'is_running': False
63      } ) ),
64      'items': empty()
65    } ) )
66  )
67
68
69@IsolatedYcmd()
70def ServerManagement_StopServer_Clean_test( app ):
71  StartClangd( app )
72  StopCompleterServer( app, 'cpp', '' )
73  CheckStopped( app )
74
75
76@IsolatedYcmd()
77@patch( 'os.remove', side_effect = OSError )
78@patch( 'ycmd.utils.WaitUntilProcessIsTerminated',
79        MockProcessTerminationTimingOut )
80def ServerManagement_StopServer_Unclean_test( rm, app ):
81  StartClangd( app )
82  StopCompleterServer( app, 'cpp', '' )
83  CheckStopped( app )
84
85
86@IsolatedYcmd()
87def ServerManagement_StopServer_Twice_test( app ):
88  StartClangd( app )
89  StopCompleterServer( app, 'cpp', '' )
90  CheckStopped( app )
91  StopCompleterServer( app, 'cpp', '' )
92  CheckStopped( app )
93
94
95@IsolatedYcmd()
96def ServerManagement_StopServer_Killed_test( app ):
97  StartClangd( app )
98  process = psutil.Process( GetPid( app ) )
99  process.terminate()
100  process.wait( timeout = 5 )
101  StopCompleterServer( app, 'cpp', '' )
102  CheckStopped( app )
103
104
105@IsolatedYcmd()
106def ServerManagement_ServerDiesWhileShuttingDown_test( app ):
107  StartClangd( app )
108  process = psutil.Process( GetPid( app ) )
109  completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] )
110
111  # We issue a shutdown but make sure it never reaches server by mocking
112  # WriteData in Connection. Then we kill the server and check shutdown still
113  # succeeds.
114  with patch.object( completer.GetConnection(), 'WriteData' ):
115    stop_server_task = utils.StartThread( StopCompleterServer, app, 'cpp', '' )
116    process.terminate()
117    stop_server_task.join()
118
119  CheckStopped( app )
120
121
122@IsolatedYcmd()
123def ServerManagement_ConnectionRaisesWhileShuttingDown_test( app ):
124  StartClangd( app )
125  process = psutil.Process( GetPid( app ) )
126  completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] )
127
128  # We issue a shutdown but make sure it never reaches server by mocking
129  # WriteData in Connection. Then we kill the server and check shutdown still
130  # succeeds.
131  with patch.object( completer.GetConnection(), 'GetResponse',
132                     side_effect = RuntimeError ):
133    StopCompleterServer( app, 'cpp', '' )
134
135  CheckStopped( app )
136  if process.is_running():
137    process.terminate()
138    raise AssertionError( 'Termination failed' )
139
140
141@IsolatedYcmd()
142def ServerManagement_RestartServer_test( app ):
143  StartClangd( app, PathToTestFile( 'basic.cpp' ) )
144
145  assert_that(
146    GetDebugInfo( app ),
147    CompleterProjectDirectoryMatcher( PathToTestFile() ) )
148
149  app.post_json(
150    '/run_completer_command',
151    BuildRequest(
152      filepath = PathToTestFile( 'test-include', 'main.cpp' ),
153      filetype = 'cpp',
154      command_arguments = [ 'RestartServer' ],
155    ),
156  )
157
158  WaitUntilCompleterServerReady( app, 'cpp' )
159
160  assert_that(
161    GetDebugInfo( app ),
162    has_entry( 'completer', has_entries( {
163      'name': 'C-family',
164      'servers': contains_exactly( has_entries( {
165        'name': 'Clangd',
166        'is_running': True,
167        'extras': has_item( has_entries( {
168          'key': 'Project Directory',
169          'value': PathToTestFile( 'test-include' ),
170        } ) )
171      } ) )
172    } ) )
173  )
174
175
176def Dummy_test():
177  # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51
178  assert True
179