diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2ceec964c8..41be738088 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -239,6 +239,9 @@ jobs:
           ~/.npm
       name: Cache test deps
 
+    - uses: actions/setup-dotnet@v4
+      with:
+        dotnet-version: '8'
     - name: Install Java
       if: matrix.benchmark == false
       uses: actions/setup-java@v4
diff --git a/build.py b/build.py
index fba9421cb0..1f24f0af68 100755
--- a/build.py
+++ b/build.py
@@ -903,52 +903,45 @@ def GetCsCompleterDataForPlatform():
   ####################################
   DATA = {
     'win32': {
-      'version': 'v1.37.11',
+      'version': 'v1.39.12',
       'download_url': ( 'https://github.com/OmniSharp/omnisharp-roslyn/release'
-                        's/download/v1.37.11/omnisharp.http-win-x86.zip' ),
-      'file_name': 'omnisharp.http-win-x86.zip',
-      'check_sum': ( '461544056b144c97e8413de8c1aa1ddd9e2902f5a9f2223af8046d65'
-                     '4d95f2a0' ),
+                        's/download/v1.39.12/omnisharp-win-x86-net6.0.zip' ),
+      'file_name': 'omnisharp-win-x86-net6.0.zip',
+      'check_sum': ( 'b38cfc810bbab7f922130d2c8df266454b7be038ef73d278ae082073'
+                     '58318eec' ),
     },
     'win64': {
-      'version': 'v1.37.11',
+      'version': 'v1.39.12',
       'download_url': ( 'https://github.com/OmniSharp/omnisharp-roslyn/release'
-                        's/download/v1.37.11/omnisharp.http-win-x64.zip' ),
-      'file_name': 'omnisharp.http-win-x64.zip',
-      'check_sum': ( '7f6f0abfac00d028b90aaf1f56813e4fbb73d84bdf2c4704862aa976'
-                     '1b61a59c' ),
+                        's/download/v1.39.12/omnisharp-win-x64-net6.0.zip' ),
+      'file_name': 'omnisharp-win-x64-net6.0.zip',
+      'check_sum': ( '21bb3f7d990b6d464a748e9c11731582caeeaab87d7f749edeacfe13'
+                     '6a09c13e' ),
     },
     'macos': {
-      'version': 'v1.37.11',
+      'version': 'v1.39.12',
       'download_url': ( 'https://github.com/OmniSharp/omnisharp-roslyn/release'
-                        's/download/v1.37.11/omnisharp.http-osx.tar.gz' ),
-      'file_name': 'omnisharp.http-osx.tar.gz',
-      'check_sum': ( '84b84a8a3cb8fd3986ea795d9230457c43bf130b482fcb77fef57c67'
-                     'e151828a' ),
-    },
-    'linux32': {
-      'version': 'v1.37.11',
-      'download_url': ( 'https://github.com/OmniSharp/omnisharp-roslyn/release'
-                        's/download/v1.37.11/omnisharp.http-linux-x86.tar.gz' ),
-      'file_name': 'omnisharp.http-linux-x86.tar.gz',
-      'check_sum': ( 'a5ab39380a5d230c75f08bf552980cdc5bd8c31a43348acbfa66f1f4'
-                     '6f12851f' ),
+                        's/download/v1.39.12/omnisharp-osx-arm64-net6.0.tar.gz'
+                        '' ),
+      'file_name': 'omnisharp-osx-arm64-net6.0.tar.gz',
+      'check_sum': ( '27db0ded7bf9b1c90155e01a762ea3a39c7da5c26716211bb367886c'
+                     'e27b5ac2' ),
     },
     'linux64': {
-      'version': 'v1.37.11',
+      'version': 'v1.39.12',
       'download_url': ( 'https://github.com/OmniSharp/omnisharp-roslyn/release'
-                        's/download/v1.37.11/omnisharp.http-linux-x64.tar.gz' ),
-      'file_name': 'omnisharp.http-linux-x64.tar.gz',
-      'check_sum': ( '9a6e9a246babd777229eebb57f0bee86e7ef5da271c67275eae5ed9d'
-                     '7b0ad563' ),
+                        's/download/v1.39.12/omnisharp-linux-x64-net6.0.tar.gz'
+                        '' ),
+      'file_name': 'omnisharp-linux-x64-net6.0.tar.gz',
+      'check_sum': ( 'e6496db73f44005b6c750d5f2da7d752edd181cde7e07062944da816'
+                     '95428f65' ),
     },
   }
   if OnWindows():
     return DATA[ 'win64' if IS_64BIT else 'win32' ]
-  else:
-    if OnMac():
-      return DATA[ 'macos' ]
-    return DATA[ 'linux64' if IS_64BIT else 'linux32' ]
+  if OnMac():
+    return DATA[ 'macos' ]
+  return DATA[ 'linux64' ]
 
 
 def EnableGoCompleter( args ):
diff --git a/update_omnisharp.py b/update_omnisharp.py
index 566f186ce4..91b541af1b 100755
--- a/update_omnisharp.py
+++ b/update_omnisharp.py
@@ -23,11 +23,10 @@
                "releases/{version}/{file_name}" ),
 }
 FILE_NAME = {
-    'win32': 'omnisharp.http-win-x86.zip',
-    'win64': 'omnisharp.http-win-x64.zip',
-    'macos': 'omnisharp.http-osx.tar.gz',
-  'linux32': 'omnisharp.http-linux-x86.tar.gz',
-  'linux64': 'omnisharp.http-linux-x64.tar.gz',
+    'win32': 'omnisharp-win-x86-net6.0.zip',
+    'win64': 'omnisharp-win-x64-net6.0.zip',
+    'macos': 'omnisharp-osx-arm64-net6.0.tar.gz',
+  'linux64': 'omnisharp-linux-x64-net6.0.tar.gz',
 }
 
 
diff --git a/ycmd/completers/cs/cs_completer.py b/ycmd/completers/cs/cs_completer.py
index 18f52691a4..eb0b74ba3b 100644
--- a/ycmd/completers/cs/cs_completer.py
+++ b/ycmd/completers/cs/cs_completer.py
@@ -15,45 +15,30 @@
 # You should have received a copy of the GNU General Public License
 # along with ycmd.  If not, see .
 
-from collections import defaultdict
 import os
-import errno
-import json
-import time
-import urllib.request
-import urllib.error
-import threading
-from urllib.parse import urljoin
+import logging
 
-from ycmd.completers.completer import Completer
-from ycmd.completers.completer_utils import GetFileLines
-from ycmd.completers.cs import solutiondetection
-from ycmd.utils import ( ByteOffsetToCodepointOffset,
-                         CodepointOffsetToByteOffset,
-                         FindExecutable,
-                         FindExecutableWithFallback,
-                         LOGGER,
-                         ToBytes )
+from ycmd.completers.language_server import language_server_completer
 from ycmd import responses
+from ycmd.utils import ( FindExecutable,
+                         FindExecutableWithFallback,
+                         LOGGER )
 from ycmd import utils
 
-SERVER_NOT_FOUND_MSG = ( 'OmniSharp server binary not found at {0}. ' +
-                         'Did you compile it? You can do so by running ' +
-                         '"./install.py --cs-completer".' )
-INVALID_FILE_MESSAGE = 'File is invalid.'
-NO_DIAGNOSTIC_MESSAGE = 'No diagnostic for current line!'
 PATH_TO_ROSLYN_OMNISHARP = os.path.join(
   os.path.abspath( os.path.dirname( __file__ ) ),
   '..', '..', '..', 'third_party', 'omnisharp-roslyn'
 )
-PATH_TO_OMNISHARP_ROSLYN_BINARY = os.path.join(
-  PATH_TO_ROSLYN_OMNISHARP, 'Omnisharp.exe' )
-if ( not os.path.isfile( PATH_TO_OMNISHARP_ROSLYN_BINARY )
-     and os.path.isfile( os.path.join(
-       PATH_TO_ROSLYN_OMNISHARP, 'omnisharp', 'OmniSharp.exe' ) ) ):
-  PATH_TO_OMNISHARP_ROSLYN_BINARY = (
-    os.path.join( PATH_TO_ROSLYN_OMNISHARP, 'omnisharp', 'OmniSharp.exe' ) )
-LOGFILE_FORMAT = 'omnisharp_{port}_{sln}_{std}_'
+if utils.OnWindows():
+  PATH_TO_OMNISHARP_ROSLYN_BINARY = os.path.join(
+    PATH_TO_ROSLYN_OMNISHARP, 'OmniSharp.exe' )
+else:
+  PATH_TO_OMNISHARP_ROSLYN_BINARY = os.path.join(
+    PATH_TO_ROSLYN_OMNISHARP, 'OmniSharp' )
+
+
+def MonoRequired( roslyn_path: str ):
+  return not utils.OnWindows() and roslyn_path.endswith( '.exe' )
 
 
 def ShouldEnableCsCompleter( user_options ):
@@ -67,915 +52,75 @@ def ShouldEnableCsCompleter( user_options ):
     roslyn = user_roslyn_path
   else:
     roslyn = PATH_TO_OMNISHARP_ROSLYN_BINARY
-  mono = FindExecutableWithFallback( user_options[ 'mono_binary_path' ],
-                                     FindExecutable( 'mono' ) )
-  if roslyn and ( mono or utils.OnWindows() ):
-    return True
-  LOGGER.info( 'No mono executable at %s', mono )
-  return False
+  if not roslyn:
+    return False
 
+  if MonoRequired( roslyn ):
+    mono = FindExecutableWithFallback( user_options[ 'mono_binary_path' ],
+                                       FindExecutable( 'mono' ) )
+    if not mono:
+      LOGGER.info( 'No mono executable at %s', mono )
+      return False
 
-class CsharpCompleter( Completer ):
-  """
-  A Completer that uses the Omnisharp server as completion engine.
-  """
+  return True
 
+
+class CsharpCompleter( language_server_completer.LanguageServerCompleter ):
   def __init__( self, user_options ):
     super().__init__( user_options )
-    self._solution_for_file = {}
-    self._completer_per_solution = {}
-    self._diagnostic_store = None
-    self._solution_state_lock = threading.Lock()
-    self.SetSignatureHelpTriggers( [ '(', ',' ] )
     if os.path.isfile( user_options[ 'roslyn_binary_path' ] ):
       self._roslyn_path = user_options[ 'roslyn_binary_path' ]
     else:
       self._roslyn_path = PATH_TO_OMNISHARP_ROSLYN_BINARY
-    self._mono_path = FindExecutableWithFallback(
-        user_options[ 'mono_binary_path' ],
-        FindExecutable( 'mono' ) )
-
-
-  def Shutdown( self ):
-    if self.user_options[ 'auto_stop_csharp_server' ]:
-      for solutioncompleter in self._completer_per_solution.values():
-        solutioncompleter._StopServer()
-
-
-  def SupportedFiletypes( self ):
-    """ Just csharp """
-    return [ 'cs' ]
-
-
-  def _GetSolutionCompleter( self, request_data ):
-    """ Get the solution completer or create a new one if it does not already
-    exist. Use a lock to avoid creating the same solution completer multiple
-    times."""
-    solution = self._GetSolutionFile( request_data[ "filepath" ] )
-
-    with self._solution_state_lock:
-      if solution not in self._completer_per_solution:
-        keep_logfiles = self.user_options[ 'server_keep_logfiles' ]
-        desired_omnisharp_port = self.user_options.get( 'csharp_server_port' )
-        completer = CsharpSolutionCompleter( solution,
-                                             keep_logfiles,
-                                             desired_omnisharp_port,
-                                             self._roslyn_path,
-                                             self._mono_path )
-        self._completer_per_solution[ solution ] = completer
-
-    return self._completer_per_solution[ solution ]
-
-
-  def SignatureHelpAvailable( self ):
-    if not self.ServerIsHealthy():
-      return responses.SignatureHelpAvailalability.PENDING
-    return responses.SignatureHelpAvailalability.AVAILABLE
-
-
-  def ComputeSignaturesInner( self, request_data ):
-    response = self._SolutionSubcommand( request_data, '_SignatureHelp' )
-
-    if response is None:
-      return {}
-
-    signatures = response[ 'Signatures' ]
-
-    def MakeSignature( s ):
-      sig_label = s[ 'Label' ]
-      end = 0
-      parameters = []
-      for arg in s[ 'Parameters' ]:
-        arg_label = arg[ 'Label' ]
-        begin = sig_label.find( arg_label, end )
-        end = begin + len( arg_label )
-        parameters.append( {
-          'documentation': arg.get( 'Documentation', '' ),
-          'label': [ CodepointOffsetToByteOffset( sig_label, begin ),
-                     CodepointOffsetToByteOffset( sig_label, end ) ]
-        } )
-
-      return {
-        'documentation': s.get( 'Documentation', '' ),
-        'label': sig_label,
-        'parameters': parameters
-      }
-
-    return {
-      'activeSignature': response[ 'ActiveSignature' ],
-      'activeParameter': response[ 'ActiveParameter' ],
-      'signatures': [ MakeSignature( s ) for s in signatures ]
-    }
-
-
-  def ResolveFixit( self, request_data ):
-    return self._SolutionSubcommand( request_data, '_ResolveFixIt' )
-
-
-  def ComputeCandidatesInner( self, request_data ):
-    solutioncompleter = self._GetSolutionCompleter( request_data )
-    return [ responses.BuildCompletionData(
-                completion[ 'CompletionText' ],
-                completion[ 'DisplayText' ],
-                completion[ 'Description' ],
-                None,
-                completion[ 'Kind' ] )
-             for completion
-             in solutioncompleter._GetCompletions( request_data ) ]
-
-
-  def GetSubcommandsMap( self ):
-    return {
-      'StopServer'                       : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_StopServer',
-                                   no_request_data = True ) ),
-      'RestartServer'                    : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_RestartServer',
-                                   no_request_data = True ) ),
-      'GoToDefinition'                   : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GoToDefinition' ) ),
-      'GoToDeclaration'                  : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GoToDefinition' ) ),
-      'GoTo'                             : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GoToImplementation',
-                                   fallback_to_declaration = True ) ),
-      'GoToDefinitionElseDeclaration'    : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GoToDefinition' ) ),
-      'GoToReferences'                   : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GoToReferences' ) ),
-      'GoToImplementation'               : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GoToImplementation',
-                                   fallback_to_declaration = False ) ),
-      'GoToImplementationElseDeclaration': ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GoToImplementation',
-                                   fallback_to_declaration = True ) ),
-      'GetType'                          : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GetType' ) ),
-      'Format'                           : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_Format' ) ),
-      'FixIt'                            : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_FixIt' ) ),
-      'GetDoc'                           : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GetDoc' ) ),
-      'GoToSymbol'                       : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GoToSymbol',
-                                   args = args ) ),
-      'OrganizeImports'                  : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_OrganizeImports' ) ),
-      'RefactorRename'                   : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_RefactorRename',
-                                   args = args ) ),
-      'GoToDocumentOutline'              : ( lambda self, request_data, args:
-         self._SolutionSubcommand( request_data,
-                                   method = '_GoToDocumentOutline' ) ),
-    }
-
-
-  def _SolutionSubcommand( self, request_data, method,
-                           no_request_data = False, **kwargs ):
-    solutioncompleter = self._GetSolutionCompleter( request_data )
-    if not no_request_data:
-      kwargs[ 'request_data' ] = request_data
-    return getattr( solutioncompleter, method )( **kwargs )
-
-
-  def OnFileReadyToParse( self, request_data ):
-    solutioncompleter = self._GetSolutionCompleter( request_data )
-
-    # Only start the server associated to this solution if the option to
-    # automatically start one is set and no server process is already running.
-    if ( self.user_options[ 'auto_start_csharp_server' ]
-         and not solutioncompleter._ServerIsRunning() ):
-      solutioncompleter._StartServer()
-      return
-
-    # Bail out if the server is unresponsive. We don't start or restart the
-    # server in this case because current one may still be warming up.
-    if not solutioncompleter.ServerIsHealthy():
-      return
-
-    errors = solutioncompleter.CodeCheck( request_data )
-
-    diagnostics = [ self._QuickFixToDiagnostic( request_data, x ) for x in
-                    errors[ "QuickFixes" ] ]
-
-    self._diagnostic_store = DiagnosticsToDiagStructure( diagnostics )
-
-    return responses.BuildDiagnosticResponse( diagnostics,
-                                              request_data[ 'filepath' ],
-                                              self.max_diagnostics_to_display )
-
-
-  def _QuickFixToDiagnostic( self, request_data, quick_fix ):
-    filename = quick_fix[ "FileName" ]
-    # NOTE: end of diagnostic range returned by the OmniSharp server is not
-    # included.
-    location = _BuildLocation( request_data,
-                               filename,
-                               quick_fix[ 'Line' ],
-                               quick_fix[ 'Column' ] )
-    location_end = _BuildLocation( request_data,
-                                   filename,
-                                   quick_fix[ 'EndLine' ],
-                                   quick_fix[ 'EndColumn' ] )
-    if not location_end:
-      location_end = location
-    location_extent = responses.Range( location, location_end )
-    return responses.Diagnostic( [],
-                                 location,
-                                 location_extent,
-                                 quick_fix[ 'Text' ],
-                                 quick_fix[ 'LogLevel' ].upper() )
-
-
-  def GetDetailedDiagnostic( self, request_data ):
-    current_line = request_data[ 'line_num' ]
-    current_column = request_data[ 'column_num' ]
-    current_file = request_data[ 'filepath' ]
-
-    if not self._diagnostic_store:
-      raise ValueError( NO_DIAGNOSTIC_MESSAGE )
-
-    diagnostics = self._diagnostic_store[ current_file ][ current_line ]
-    if not diagnostics:
-      raise ValueError( NO_DIAGNOSTIC_MESSAGE )
-
-    # Prefer errors to warnings and warnings to infos.
-    diagnostics.sort( key = _CsDiagnosticToLspSeverity )
-
-    closest_diagnostic = None
-    distance_to_closest_diagnostic = 999
-
-    # FIXME: all of these calculations are currently working with byte
-    # offsets, which are technically incorrect. We should be working with
-    # codepoint offsets, as we want the nearest character-wise diagnostic
-    for diagnostic in diagnostics:
-      distance = abs( current_column - diagnostic.location_.column_number_ )
-      if distance < distance_to_closest_diagnostic:
-        distance_to_closest_diagnostic = distance
-        closest_diagnostic = diagnostic
-
-    return responses.BuildDisplayMessageResponse(
-      closest_diagnostic.text_ )
-
-
-  def DebugInfo( self, request_data ):
-    try:
-      completer = self._GetSolutionCompleter( request_data )
-    except RuntimeError:
-      omnisharp_server = responses.DebugInfoServer(
-        name = 'OmniSharp',
-        handle = None,
-        executable = self._roslyn_path )
-
-      return responses.BuildDebugInfoResponse( name = 'C#',
-                                               servers = [ omnisharp_server ] )
-
-    with completer._server_state_lock:
-      solution_item = responses.DebugInfoItem(
-        key = 'solution',
-        value = completer._solution_path )
-
-      omnisharp_server = responses.DebugInfoServer(
-        name = 'OmniSharp',
-        handle = completer._omnisharp_phandle,
-        executable = ' '.join( completer._ConstructOmnisharpCommand() ),
-        address = 'localhost',
-        port = completer._omnisharp_port,
-        logfiles = [ completer._filename_stdout, completer._filename_stderr ],
-        extras = [ solution_item ] )
-
-      return responses.BuildDebugInfoResponse( name = 'C#',
-                                               servers = [ omnisharp_server ] )
-
-
-  def ServerIsHealthy( self ):
-    """ Check if our OmniSharp server is healthy (up and serving). """
-    return self._CheckAllRunning( lambda i: i.ServerIsHealthy() )
-
-
-  def ServerIsReady( self ):
-    """ Check if our OmniSharp server is ready (loaded solution file)."""
-    return self._CheckAllRunning( lambda i: i.ServerIsReady() )
-
-
-  def _CheckAllRunning( self, action ):
-    solutioncompleters = self._completer_per_solution.values()
-    return all( action( completer ) for completer in solutioncompleters
-                if completer._ServerIsRunning() )
-
-
-  def _GetSolutionFile( self, filepath ):
-    if filepath not in self._solution_for_file:
-      # NOTE: detection could throw an exception if an extra_conf_store needs
-      # to be confirmed
-      path_to_solutionfile = solutiondetection.FindSolutionPath( filepath )
-      if not path_to_solutionfile:
-        raise RuntimeError( 'Autodetection of solution file failed.' )
-      self._solution_for_file[ filepath ] = path_to_solutionfile
-
-    return self._solution_for_file[ filepath ]
-
-
-class CsharpSolutionCompleter( object ):
-  def __init__( self,
-                solution_path,
-                keep_logfiles,
-                desired_omnisharp_port,
-                roslyn_path,
-                mono_path ):
-    self._solution_path = solution_path
-    self._keep_logfiles = keep_logfiles
-    self._filename_stderr = None
-    self._filename_stdout = None
-    self._omnisharp_command = None
-    self._omnisharp_port = None
-    self._omnisharp_phandle = None
-    self._desired_omnisharp_port = desired_omnisharp_port
-    self._server_state_lock = threading.Lock()
-    self._roslyn_path = roslyn_path
-    self._mono_path = mono_path
-
-
-  def CodeCheck( self, request_data ):
-    filename = request_data[ 'filepath' ]
-    if not filename:
-      raise ValueError( INVALID_FILE_MESSAGE )
-
-    return self._GetResponse( '/codecheck',
-                              self._DefaultParameters( request_data ) )
-
-
-  def _StartServer( self ):
-    with self._server_state_lock:
-      return self._StartServerNoLock()
-
-
-  def _ConstructOmnisharpCommand( self ):
-    if self._omnisharp_command:
-      return self._omnisharp_command
-
-    self._ChooseOmnisharpPort()
-    self._omnisharp_command = [ self._roslyn_path,
-                                '-p',
-                                str( self._omnisharp_port ),
-                                '-s',
-                                str( self._solution_path ) ]
-
-    if ( not utils.OnWindows()
-         and self._roslyn_path.endswith( '.exe' ) ):
-      self._omnisharp_command.insert( 0, self._mono_path )
-
-    return self._omnisharp_command
-
-
-  def _StartServerNoLock( self ):
-    """ Start the OmniSharp server if not already running. Use a lock to avoid
-    starting the server multiple times for the same solution. """
-    if self._ServerIsRunning():
-      return
-
-    LOGGER.info( 'Starting OmniSharp server' )
-    LOGGER.info( 'Loading solution file %s', self._solution_path )
-
-    command = self._ConstructOmnisharpCommand()
-    LOGGER.info( 'Starting OmniSharp server with: %s', command )
-
-    solutionfile = os.path.basename( self._solution_path )
-    self._filename_stdout = utils.CreateLogfile(
-        LOGFILE_FORMAT.format( port = self._omnisharp_port,
-                               sln = solutionfile,
-                               std = 'stdout' ) )
-    self._filename_stderr = utils.CreateLogfile(
-        LOGFILE_FORMAT.format( port = self._omnisharp_port,
-                               sln = solutionfile,
-                               std = 'stderr' ) )
-
-    with utils.OpenForStdHandle( self._filename_stderr ) as fstderr:
-      with utils.OpenForStdHandle( self._filename_stdout ) as fstdout:
-        self._omnisharp_phandle = utils.SafePopen(
-            command, stdout = fstdout, stderr = fstderr )
+    self._mono = FindExecutableWithFallback( user_options[ 'mono_binary_path' ],
+                                             FindExecutable( 'mono' ) )
 
-    LOGGER.info( 'Started OmniSharp server' )
 
+  def GetServerName( self ):
+    return 'OmniSharp-Roslyn'
 
-  def _StopServer( self ):
-    with self._server_state_lock:
-      return self._StopServerNoLock()
 
+  def GetProjectRootFiles( self ):
+    return [ '*.csproj' ]
 
-  def _StopServerNoLock( self ):
-    """ Stop the OmniSharp server using a lock. """
-    if self._ServerIsRunning():
-      LOGGER.info( 'Stopping OmniSharp server with PID %s',
-                   self._omnisharp_phandle.pid )
-      try:
-        self._TryToStopServer()
-        self._ForceStopServer()
-        utils.WaitUntilProcessIsTerminated( self._omnisharp_phandle,
-                                            timeout = 5 )
-        LOGGER.info( 'OmniSharp server stopped' )
-      except Exception:
-        LOGGER.exception( 'Error while stopping OmniSharp server' )
 
-    self._CleanUp()
+  def GetCommandLine( self ):
+    # TODO: User options?
+    cmdline = [ self._roslyn_path, '-lsp' ]
+    if utils.LOGGER.isEnabledFor( logging.DEBUG ):
+      cmdline += [ '-v' ]
+    if MonoRequired( self._roslyn_path ):
+      cmdline.insert( 0, self._mono )
+    return cmdline
 
 
-  def _TryToStopServer( self ):
-    for _ in range( 5 ):
-      try:
-        self._GetResponse( '/stopserver', timeout = .5 )
-      except Exception:
-        pass
-      for _ in range( 10 ):
-        if not self._ServerIsRunning():
-          return
-        time.sleep( .1 )
-
-
-  def _ForceStopServer( self ):
-    # Kill it if it's still up
-    phandle = self._omnisharp_phandle
-    if phandle is not None:
-      LOGGER.info( 'Killing OmniSharp server' )
-      for stream in [ phandle.stderr, phandle.stdout ]:
-        if stream is not None:
-          stream.close()
-      try:
-        phandle.kill()
-      except OSError as e:
-        if e.errno == errno.ESRCH: # No such process
-          pass
-        else:
-          raise
-
-
-  def _CleanUp( self ):
-    self._omnisharp_command = None
-    self._omnisharp_port = None
-    self._omnisharp_phandle = None
-    if not self._keep_logfiles:
-      if self._filename_stdout:
-        utils.RemoveIfExists( self._filename_stdout )
-        self._filename_stdout = None
-      if self._filename_stderr:
-        utils.RemoveIfExists( self._filename_stderr )
-        self._filename_stderr = None
-
-
-  def _RestartServer( self ):
-    """ Restarts the OmniSharp server using a lock. """
-    with self._server_state_lock:
-      self._StopServerNoLock()
-      return self._StartServerNoLock()
-
-
-  def _GetCompletions( self, request_data ):
-    """ Ask server for completions """
-    parameters = self._DefaultParameters( request_data )
-    parameters[ 'WantSnippet' ] = False
-    parameters[ 'WantKind' ] = True
-    parameters[ 'WantReturnType' ] = False
-    parameters[ 'WantDocumentationForEveryCompletionResult' ] = True
-    completions = self._GetResponse( '/autocomplete', parameters )
-    return completions if completions is not None else []
-
-
-  def _GoToDefinition( self, request_data ):
-    """ Jump to definition of identifier under cursor """
-    definition = self._GetResponse( '/gotodefinition',
-                                    self._DefaultParameters( request_data ) )
-    if definition[ 'FileName' ] is not None:
-      filepath = definition[ 'FileName' ]
-      return responses.BuildGoToResponseFromLocation(
-        _BuildLocation( request_data,
-                        filepath,
-                        definition[ 'Line' ],
-                        definition[ 'Column' ] ) )
-    else:
-      raise RuntimeError( 'Can\'t jump to definition' )
-
-
-  def _GoToImplementation( self, request_data, fallback_to_declaration ):
-    """ Jump to implementation of identifier under cursor """
-    try:
-      implementation = self._GetResponse(
-          '/findimplementations',
-          self._DefaultParameters( request_data ) )
-    except ValueError:
-      implementation = { 'QuickFixes': None }
-
-    quickfixes = implementation[ 'QuickFixes' ]
-    if quickfixes:
-      if len( quickfixes ) == 1:
-        impl = quickfixes[ 0 ]
-        return responses.BuildGoToResponseFromLocation(
-          _BuildLocation(
-            request_data,
-            impl[ 'FileName' ],
-            impl[ 'Line' ],
-            impl[ 'Column' ] ) )
-      else:
-        return [ responses.BuildGoToResponseFromLocation(
-                   _BuildLocation( request_data,
-                                   x[ 'FileName' ],
-                                   x[ 'Line' ],
-                                   x[ 'Column' ] ) )
-                 for x in quickfixes ]
-    else:
-      if ( fallback_to_declaration ):
-        return self._GoToDefinition( request_data )
-      elif quickfixes is None:
-        raise RuntimeError( 'Can\'t jump to implementation' )
-      else:
-        raise RuntimeError( 'No implementations found' )
-
-
-  def _SignatureHelp( self, request_data ):
-    request = self._DefaultParameters( request_data )
-    return self._GetResponse( '/signatureHelp', request )
-
-
-  def _RefactorRename( self, request_data, args ):
-    request = self._DefaultParameters( request_data )
-    if len( args ) != 1:
-      raise ValueError( 'Please specify a new name to rename it to.\n'
-                        'Usage: RefactorRename ' )
-    request[ 'RenameTo' ] = args[ 0 ]
-    request[ 'WantsTextChanges' ] = True
-    response = self._GetResponse( '/rename', request )
-    fixit = _ModifiedFilesToFixIt( response[ 'Changes' ], request_data )
-    return responses.BuildFixItResponse( [ fixit ] )
-
-
-  def _GoToSymbol( self, request_data, args ):
-    request = self._DefaultParameters( request_data )
-    request.update( {
-      'Language': 'C#',
-      'Filter': args[ 0 ]
-    } )
-    response = self._GetResponse( '/findsymbols', request )
-
-    quickfixes = response[ 'QuickFixes' ]
-    if quickfixes:
-      if len( quickfixes ) == 1:
-        ref = quickfixes[ 0 ]
-        return responses.BuildGoToResponseFromLocation(
-          _BuildLocation(
-            request_data,
-            ref[ 'FileName' ],
-            ref[ 'Line' ],
-            ref[ 'Column' ] ),
-          ref[ 'Text' ] )
-      else:
-        goto_locations = []
-        for ref in quickfixes:
-          goto_locations.append(
-            responses.BuildGoToResponseFromLocation(
-              _BuildLocation( request_data,
-                              ref[ 'FileName' ],
-                              ref[ 'Line' ],
-                              ref[ 'Column' ] ),
-              ref[ 'Text' ] ) )
-
-        return goto_locations
-    else:
-      raise RuntimeError( 'No symbols found' )
-
-
-  def _GoToDocumentOutline( self, request_data ):
-    request = self._DefaultParameters( request_data )
-    response = self._GetResponse( '/currentfilemembersasflat', request )
-    if response is not None and len( response ) > 0:
-      goto_locations = []
-      for ref in response:
-        goto_locations.append(
-          responses.BuildGoToResponseFromLocation(
-            _BuildLocation( request_data,
-                            ref[ 'FileName' ],
-                            ref[ 'Line' ],
-                            ref[ 'Column' ] ),
-            ref[ 'Text' ] ) )
-      if len( goto_locations ) > 1:
-        return goto_locations
-      return goto_locations[ 0 ]
-    else:
-      raise RuntimeError( 'No symbols found' )
-
-  def _GoToReferences( self, request_data ):
-    """ Jump to references of identifier under cursor """
-    # _GetResponse can throw. Original code by @mispencer
-    # wrapped it in a try/except and set `reference` to `{ 'QuickFixes': None }`
-    # After being unable to hit that case with tests,
-    # that code path was thrown away.
-    reference = self._GetResponse(
-       '/findusages',
-       self._DefaultParameters( request_data ) )
-
-    quickfixes = reference[ 'QuickFixes' ]
-    if quickfixes:
-      if len( quickfixes ) == 1:
-        ref = quickfixes[ 0 ]
-        return responses.BuildGoToResponseFromLocation(
-          _BuildLocation(
-            request_data,
-            ref[ 'FileName' ],
-            ref[ 'Line' ],
-            ref[ 'Column' ] ) )
-      else:
-        return [ responses.BuildGoToResponseFromLocation(
-                   _BuildLocation( request_data,
-                                   ref[ 'FileName' ],
-                                   ref[ 'Line' ],
-                                   ref[ 'Column' ] ) )
-                 for ref in quickfixes ]
-    else:
-      raise RuntimeError( 'No references found' )
-
-
-  def _GetType( self, request_data ):
-    request = self._DefaultParameters( request_data )
-    request[ "IncludeDocumentation" ] = False
-
-    result = self._GetResponse( '/typelookup', request )
-    message = result[ "Type" ]
-
-    if not message:
-      raise RuntimeError( 'No type info available.' )
-    return responses.BuildDisplayMessageResponse( message )
-
-
-  def _OrganizeImports( self, request_data ):
-    request = self._DefaultParameters( request_data )
-    request[ 'WantsTextChanges' ] = True
-    result = self._GetResponse( '/fixusings', request )
-    fixit = responses.FixIt(
-      _BuildLocation(
-        request_data,
-        request_data[ 'filepath' ],
-        request_data[ 'line_num' ],
-        request_data[ 'column_codepoint' ] ),
-      _LinePositionSpanTextChangeToFixItChunks(
-        result[ 'Changes' ],
-        request_data[ 'filepath' ],
-        request_data ) )
-    return responses.BuildFixItResponse( [ fixit ] )
-
-
-  def _Format( self, request_data ):
-    request = self._DefaultParameters( request_data )
-    request[ 'WantsTextChanges' ] = True
-    if 'range' in request_data:
-      lines = request_data[ 'lines' ]
-      start = request_data[ 'range' ][ 'start' ]
-      start_line_num = start[ 'line_num' ]
-      start_line_value = lines[ start_line_num ]
-
-      start_codepoint = ByteOffsetToCodepointOffset( start_line_value,
-                                                     start[ 'column_num' ] )
-
-      end = request_data[ 'range' ][ 'end' ]
-      end_line_num = end[ 'line_num' ]
-      end_line_value = lines[ end_line_num ]
-      end_codepoint = ByteOffsetToCodepointOffset( end_line_value,
-                                                   end[ 'column_num' ] )
-      request.update( {
-        'line': start_line_num,
-        'column': start_codepoint,
-        'EndLine': end_line_num,
-        'EndColumn': end_codepoint
-      } )
-      result = self._GetResponse( '/formatRange', request )
-    else:
-      result = self._GetResponse( '/codeformat', request )
-
-    fixit = responses.FixIt(
-      _BuildLocation(
-        request_data,
-        request_data[ 'filepath' ],
-        request_data[ 'line_num' ],
-        request_data[ 'column_codepoint' ] ),
-      _LinePositionSpanTextChangeToFixItChunks(
-        result[ 'Changes' ],
-        request_data[ 'filepath' ],
-        request_data ) )
-    return responses.BuildFixItResponse( [ fixit ] )
-
-
-  def _FixIt( self, request_data ):
-    request = self._DefaultParameters( request_data )
-    request[ 'WantsTextChanges' ] = True
-
-    result = self._GetResponse( '/getcodeactions', request )
-
-    fixits = []
-    for i, code_action_name in enumerate( result[ 'CodeActions' ] ):
-      fixit = responses.UnresolvedFixIt( { 'index': i }, code_action_name )
-      fixits.append( fixit )
-
-    if len( fixits ) == 1:
-      fixit = fixits[ 0 ]
-      fixit = { 'command': fixit.command, 'resolve': fixit.resolve }
-      return self._ResolveFixIt( request_data, fixit )
-
-    return responses.BuildFixItResponse( fixits )
-
-
-  def _ResolveFixIt( self, request_data, unresolved_fixit = None ):
-    fixit = unresolved_fixit if unresolved_fixit else request_data[ 'fixit' ]
-    if not fixit[ 'resolve' ]:
-      return { 'fixits': [ fixit ] }
-    fixit = fixit[ 'command' ]
-    code_action = fixit[ 'index' ]
-    request = self._DefaultParameters( request_data )
-    request.update( {
-      'CodeAction': code_action,
-      'WantsTextChanges': True,
-    } )
-    response = self._GetResponse( '/runcodeaction', request )
-    fixit = responses.FixIt(
-      _BuildLocation(
-        request_data,
-        request_data[ 'filepath' ],
-        request_data[ 'line_num' ],
-        request_data[ 'column_codepoint' ] ),
-      _LinePositionSpanTextChangeToFixItChunks(
-        response[ 'Changes' ],
-        request_data[ 'filepath' ],
-        request_data ),
-      response[ 'Text' ] )
-    # The sort is necessary to keep the tests stable.
-    # Python's sort() is stable, so it won't mess up the order within a file.
-    fixit.chunks.sort( key = lambda c: c.range.start_.filename_ )
-    return responses.BuildFixItResponse( [ fixit ] )
-
+  def SupportedFiletypes( self ):
+    return [ 'cs' ]
 
-  def _GetDoc( self, request_data ):
-    request = self._DefaultParameters( request_data )
-    request[ "IncludeDocumentation" ] = True
 
-    result = self._GetResponse( '/typelookup', request )
-    message = result.get( 'Type' ) or ''
+  def GetType( self, request_data ):
+    hover_value = self.GetHoverResponse( request_data )[ 'value' ]
+    if not hover_value:
+      raise RuntimeError( 'No type found.' )
+    value = hover_value.split( '\n', maxsplit = 2 )[ 1 ]
+    return responses.BuildDisplayMessageResponse( value )
 
-    if ( result[ "Documentation" ] ):
-      message += "\n" + result[ "Documentation" ]
 
-    if not message:
+  def GetDoc( self, request_data ):
+    hover_value = self.GetHoverResponse( request_data )[ 'value' ]
+    if not hover_value:
       raise RuntimeError( 'No documentation available.' )
-    return responses.BuildDetailedInfoResponse( message.strip() )
-
-
-  def _DefaultParameters( self, request_data ):
-    """ Some very common request parameters """
-    parameters = {}
-    parameters[ 'line' ] = request_data[ 'line_num' ]
-    parameters[ 'column' ] = request_data[ 'column_codepoint' ]
-
-    filepath = request_data[ 'filepath' ]
-    parameters[ 'buffer' ] = (
-      request_data[ 'file_data' ][ filepath ][ 'contents' ] )
-    parameters[ 'filename' ] = filepath
-    return parameters
-
-
-  def _ServerIsRunning( self ):
-    """ Check if our OmniSharp server is running (process is up)."""
-    return utils.ProcessIsRunning( self._omnisharp_phandle )
-
-
-  def ServerIsHealthy( self ):
-    """ Check if our OmniSharp server is healthy (up and serving)."""
-    if not self._ServerIsRunning():
-      return False
-
-    try:
-      return self._GetResponse( '/checkalivestatus', timeout = 3 )
-    except Exception:
-      return False
-
-
-  def ServerIsReady( self ):
-    """ Check if our OmniSharp server is ready (loaded solution file)."""
-    if not self._ServerIsRunning():
-      return False
-
-    try:
-      return self._GetResponse( '/checkreadystatus', timeout = .2 )
-    except Exception:
-      return False
-
-
-  def _ServerLocation( self ):
-    # We cannot use 127.0.0.1 like we do in other places because OmniSharp
-    # server only listens on localhost.
-    return f'http://127.0.0.1:{ self._omnisharp_port }'
-
-
-  def _GetResponse( self, handler, parameters = {}, timeout = None ):
-    """ Handle communication with server """
-    target = urljoin( self._ServerLocation(), handler )
-    LOGGER.debug( 'TX (%s): %s', handler, parameters )
-    try:
-      response = urllib.request.urlopen(
-        target,
-        data = ToBytes( json.dumps( parameters ) ),
-        timeout = timeout )
-      json_response = json.loads( response.read() )
-      response.close()
-    except urllib.error.HTTPError as response:
-      json_response = json.loads( response.fp.read() )
-      response.close()
-    LOGGER.debug( 'RX: %s', json_response )
-    return json_response
-
-
-  def _ChooseOmnisharpPort( self ):
-    if not self._omnisharp_port:
-      if self._desired_omnisharp_port:
-        self._omnisharp_port = int( self._desired_omnisharp_port )
-      else:
-        self._omnisharp_port = utils.GetUnusedLocalhostPort()
-    LOGGER.info( 'using port %s', self._omnisharp_port )
-
-
-def DiagnosticsToDiagStructure( diagnostics ):
-  structure = defaultdict( lambda : defaultdict( list ) )
-  for diagnostic in diagnostics:
-    structure[ diagnostic.location_.filename_ ][
-      diagnostic.location_.line_number_ ].append( diagnostic )
-  return structure
-
-
-def _BuildLocation( request_data, filename, line_num, column_num ):
-  if line_num <= 0:
-    return None
-  # OmniSharp sometimes incorrectly returns 0 for the column number. Assume the
-  # column is 1 in that case.
-  if column_num <= 0:
-    column_num = 1
-  contents = GetFileLines( request_data, filename )
-  line_value = contents[ min( len( contents ) - 1, line_num - 1 ) ]
-  return responses.Location(
-      line_num,
-      CodepointOffsetToByteOffset( line_value, column_num ),
-      filename )
-
-
-def _LinePositionSpanTextChangeToFixItChunks( chunks, filename, request_data ):
-  return [ responses.FixItChunk(
-      chunk[ 'NewText' ],
-      responses.Range(
-        _BuildLocation(
-          request_data,
-          filename,
-          chunk[ 'StartLine' ],
-          chunk[ 'StartColumn' ] ),
-        _BuildLocation(
-          request_data,
-          filename,
-          chunk[ 'EndLine' ],
-          chunk[ 'EndColumn' ] ) ) ) for chunk in chunks ]
-
-
-def _ModifiedFilesToFixIt( changes, request_data ):
-  chunks = []
-  for change in changes:
-    chunks.extend(
-      _LinePositionSpanTextChangeToFixItChunks(
-        change[ 'Changes' ],
-        change[ 'FileName' ],
-        request_data ) )
-  # The sort is necessary to keep the tests stable.
-  # Python's sort() is stable, so it won't mess up the order within a file.
-  chunks.sort( key = lambda c: c.range.start_.filename_ )
-  return responses.FixIt(
-      _BuildLocation(
-        request_data,
-        request_data[ 'filepath' ],
-        request_data[ 'line_num' ],
-        request_data[ 'column_codepoint' ] ),
-      chunks )
-
-
-def _CsDiagnosticToLspSeverity( diagnostic ):
-  if diagnostic.kind_ == 'ERROR':
-    return 1
-  if diagnostic.kind_ == 'WARNING':
-    return 2
-  return 3
+    # The response looks like this:
+    #
+    # ```csharp
+    # type info
+    # ```
+    #
+    # docstring
+    #
+    # The idea is to get rid of silly markdown backticks.
+    lines = hover_value.splitlines()
+    del lines[ 2 ]
+    del lines[ 0 ]
+    result = '\n'.join( lines )
+    return responses.BuildDetailedInfoResponse( result )
diff --git a/ycmd/completers/cs/solutiondetection.py b/ycmd/completers/cs/solutiondetection.py
deleted file mode 100644
index 8b13035a00..0000000000
--- a/ycmd/completers/cs/solutiondetection.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright (C) 2013-2020 ycmd contributors.
-#
-# This file is part of ycmd.
-#
-# ycmd is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# ycmd is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with ycmd.  If not, see .
-
-import os
-import glob
-from inspect import getfile
-from ycmd import extra_conf_store
-from ycmd.utils import LOGGER
-
-
-def FindSolutionPath( filepath ):
-  """Try to find suitable solution file given a source file path using all
-     available information sources"""
-  # try to load ycm_extra_conf
-  # if it needs to be verified, abort here and try again later
-  module = extra_conf_store.ModuleForSourceFile( filepath )
-  path_to_solutionfile = PollModule( module, filepath )
-
-  if not path_to_solutionfile:
-    # ycm_extra_conf not available or did not provide a solution file
-    path_to_solutionfile = GuessFile( filepath )
-
-  return path_to_solutionfile
-
-
-def PollModule( module, filepath ):
-  """ Try to use passed module in the selection process by calling
-  CSharpSolutionFile on it """
-  path_to_solutionfile = None
-  module_hint = None
-  if module:
-    try:
-      module_hint = module.CSharpSolutionFile( filepath )
-      LOGGER.info( 'extra_conf_store suggests %s as solution file',
-                   module_hint )
-      if module_hint:
-        # received a full path or one relative to the config's location?
-        candidates = [ module_hint,
-          os.path.join( os.path.dirname( getfile( module ) ),
-                        module_hint ) ]
-        # try the assumptions
-        for path in candidates:
-          if os.path.isfile( path ):
-            # path seems to point to a solution
-            path_to_solutionfile = path
-            LOGGER.info( 'Using solution file %s selected by extra_conf_store',
-                         path_to_solutionfile )
-            break
-    except AttributeError:
-      # the config script might not provide solution file locations
-      LOGGER.exception( 'Could not retrieve solution for %s'
-                        'from extra_conf_store', filepath )
-  return path_to_solutionfile
-
-
-def GuessFile( filepath ):
-  """ Find solution files by searching upwards in the file tree """
-  tokens = _PathComponents( filepath )
-  for i in reversed( range( len( tokens ) - 1 ) ):
-    path = os.path.join( *tokens[ : i + 1 ] )
-    candidates = glob.glob1( path, '*.sln' )
-    if len( candidates ) > 0:
-      # do the whole procedure only for the first solution file(s) you find
-      return _SolutionTestCheckHeuristics( candidates, tokens, i )
-  return None
-
-
-def _SolutionTestCheckHeuristics( candidates, tokens, i ):
-  """ Test if one of the candidate files stands out """
-  path = os.path.join( *tokens[ : i + 1 ] )
-  selection = None
-  # if there is just one file here, use that
-  if len( candidates ) == 1 :
-    selection = os.path.join( path, candidates[ 0 ] )
-    LOGGER.info( 'Selected solution file %s as it is the first one found',
-                 selection )
-
-  # there is more than one file, try some hints to decide
-  # 1. is there a solution named just like the subdirectory with the source?
-  if ( not selection and i < len( tokens ) - 1 and
-       f'{ tokens[ i + 1 ] }.sln' in candidates ):
-    selection = os.path.join( path, f'{ tokens[ i + 1 ] }.sln' )
-    LOGGER.info( 'Selected solution file %s as it matches source subfolder',
-                 selection )
-
-  # 2. is there a solution named just like the directory containing the
-  # solution?
-  if not selection and f'{ tokens[ i ] }.sln' in candidates :
-    selection = os.path.join( path, f'{ tokens[ i ] }.sln' )
-    LOGGER.info( 'Selected solution file %s as it matches containing folder',
-                 selection )
-
-  if not selection:
-    LOGGER.error( 'Could not decide between multiple solution files:\n%s',
-                  candidates )
-
-  return selection
-
-
-def _PathComponents( path ):
-  path_components = []
-  while True:
-    path, folder = os.path.split( path )
-    if folder:
-      path_components.append( folder )
-    else:
-      if path:
-        path_components.append( path )
-      break
-  path_components.reverse()
-  return path_components
diff --git a/ycmd/tests/cs/__init__.py b/ycmd/tests/cs/__init__.py
index 251421e5ce..3f57dabe3e 100644
--- a/ycmd/tests/cs/__init__.py
+++ b/ycmd/tests/cs/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 ycmd contributors
+# Copyright (C) 2024 ycmd contributors
 #
 # This file is part of ycmd.
 #
@@ -17,37 +17,36 @@
 
 import functools
 import os
-import sys
-import time
-from contextlib import contextmanager
 from ycmd.tests.test_utils import ( BuildRequest,
                                     ClearCompletionsCache,
                                     IgnoreExtraConfOutsideTestsFolder,
                                     IsolatedApp,
-                                    WaitUntilCompleterServerReady,
+                                    SetUpApp,
                                     StopCompleterServer,
-                                    SetUpApp )
+                                    WaitUntilCompleterServerReady ) # noqa
 
 shared_app = None
-# map of 'app' to filepaths
-shared_filepaths = {}
-shared_log_indexes = {}
-
-
-def PathToTestFile( *args ):
-  dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
-  return os.path.join( dir_of_current_script, 'testdata', *args )
 
 
 def setUpModule():
   global shared_app
   shared_app = SetUpApp()
+  with IgnoreExtraConfOutsideTestsFolder():
+    StartCsCompleterServerInDirectory( shared_app, PathToTestFile() )
 
 
 def tearDownModule():
-  global shared_app, shared_filepaths
-  for filepath in shared_filepaths.get( shared_app, [] ):
-    StopCompleterServer( shared_app, 'cs', filepath )
+  global shared_app
+  StopCompleterServer( shared_app, 'cs' )
+
+
+def StartCsCompleterServerInDirectory( app, directory ):
+  app.post_json( '/event_notification',
+                 BuildRequest(
+                   filepath = os.path.join( directory, 'Empty.cs' ),
+                   event_name = 'FileReadyToParse',
+                   filetype = 'cs' ) )
+  WaitUntilCompleterServerReady( app, 'cs' )
 
 
 def SharedYcmd( test ):
@@ -69,81 +68,11 @@ def Wrapper( test_case_instance, *args, **kwargs ):
         try:
           test( test_case_instance, app, *args, **kwargs )
         finally:
-          global shared_filepaths
-          for filepath in shared_filepaths.get( app, [] ):
-            StopCompleterServer( app, 'cs', filepath )
+          StopCompleterServer( app, 'cs' )
     return Wrapper
   return Decorator
 
 
-def GetDebugInfo( app, filepath ):
-  request_data = BuildRequest( filetype = 'cs', filepath = filepath )
-  return app.post_json( '/debug_info', request_data ).json
-
-
-def ReadFile( filepath, fileposition ):
-  with open( filepath, encoding = 'utf8' ) as f:
-    if fileposition:
-      f.seek( fileposition )
-    return f.read(), f.tell()
-
-
-def GetDiagnostics( app, filepath ):
-  contents, _ = ReadFile( filepath, 0 )
-
-  event_data = BuildRequest( filepath = filepath,
-                             event_name = 'FileReadyToParse',
-                             filetype = 'cs',
-                             contents = contents )
-
-  return app.post_json( '/event_notification', event_data ).json
-
-
-@contextmanager
-def WrapOmniSharpServer( app, filepath ):
-  global shared_filepaths
-  global shared_log_indexes
-
-  if filepath not in shared_filepaths.setdefault( app, [] ):
-    GetDiagnostics( app, filepath )
-    shared_filepaths[ app ].append( filepath )
-    WaitUntilCsCompleterIsReady( app, filepath )
-
-  logfiles = []
-  response = GetDebugInfo( app, filepath )
-  for server in response[ 'completer' ][ 'servers' ]:
-    logfiles.extend( server[ 'logfiles' ] )
-
-  try:
-    yield
-  finally:
-    for logfile in logfiles:
-      if os.path.isfile( logfile ):
-        log_content, log_end_position = ReadFile(
-            logfile, shared_log_indexes.get( logfile, 0 ) )
-        shared_log_indexes[ logfile ] = log_end_position
-        sys.stdout.write( f'Logfile { logfile }:\n\n' )
-        sys.stdout.write( log_content )
-        sys.stdout.write( '\n' )
-
-
-def WaitUntilCsCompleterIsReady( app, filepath ):
-  WaitUntilCompleterServerReady( app, 'cs' )
-  # Omnisharp isn't ready when it says it is, so wait until Omnisharp returns
-  # at least one diagnostic multiple times.
-  success_count = 0
-  for reraise_error in [ False ] * 39 + [ True ]:
-    try:
-      if len( GetDiagnostics( app, filepath ) ) == 0:
-        raise RuntimeError( "No diagnostic" )
-      success_count += 1
-      if success_count > 2:
-        break
-    except Exception:
-      success_count = 0
-      if reraise_error:
-        raise
-
-    time.sleep( .5 )
-  else:
-    raise RuntimeError( "Never was ready" )
+def PathToTestFile( *args ):
+  dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
+  return os.path.join( dir_of_current_script, 'testdata', *args )
diff --git a/ycmd/tests/cs/cs_completer.py b/ycmd/tests/cs/cs_completer.py
new file mode 100644
index 0000000000..88313f2f5e
--- /dev/null
+++ b/ycmd/tests/cs/cs_completer.py
@@ -0,0 +1,107 @@
+# Copyright (C) 2021 ycmd contributors
+#
+# This file is part of ycmd.
+#
+# ycmd is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ycmd is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ycmd.  If not, see .
+
+from unittest.mock import patch
+from unittest import TestCase
+from hamcrest import assert_that, equal_to
+
+from ycmd import user_options_store
+from ycmd.completers.cs.hook import GetCompleter
+from ycmd.completers.cs.cs_completer import PATH_TO_OMNISHARP_ROSLYN_BINARY
+from ycmd.tests.cs import setUpModule, tearDownModule # noqa
+
+
+class GoCompleterTest( TestCase ):
+  def test_GetCompleter_OmniSharpFound( self ):
+    assert_that( GetCompleter( user_options_store.GetAll() ) )
+
+
+  @patch( 'ycmd.completers.cs.cs_completer.PATH_TO_OMNISHARP_ROSLYN_BINARY',
+          None )
+  def test_GetCompleter_OmniSharpNotFound( self, *args ):
+    assert_that( not GetCompleter( user_options_store.GetAll() ) )
+
+
+  @patch( 'ycmd.completers.cs.cs_completer.FindExecutableWithFallback',
+          wraps = lambda x, fb: x if x == 'omnisharp' else None )
+  @patch( 'os.path.isfile', return_value = False )
+  def test_GetCompleter_CustomOmniSharpNotFound( self, *args ):
+    user_options = user_options_store.GetAll().copy(
+        roslyn_binary_path = 'omnisharp' )
+    assert_that( not GetCompleter( user_options ) )
+
+
+  @patch( 'ycmd.completers.cs.cs_completer.FindExecutableWithFallback',
+          wraps = lambda x, fb: x if x == 'omnisharp' else None )
+  @patch( 'os.path.isfile', return_value = True )
+  def test_GetCompleter_CustomOmniSharpFound_MonoNotRequired( self, *args ):
+    user_options = user_options_store.GetAll().copy(
+        roslyn_binary_path = 'omnisharp' )
+    assert_that( GetCompleter( user_options ) )
+
+
+  @patch( 'ycmd.completers.cs.cs_completer.FindExecutableWithFallback',
+          wraps = lambda x, fb: x if x in ( 'mono',
+                                            'omnisharp.exe' ) else None )
+  @patch( 'os.path.isfile', return_value = True )
+  def test_GetCompleter_CustomOmniSharpFound_MonoRequiredAndFound( self,
+                                                                   *args ):
+    user_options = user_options_store.GetAll().copy(
+        roslyn_binary_path = 'omnisharp.exe',
+        mono_binary_path = 'mono' )
+    assert_that( GetCompleter( user_options ) )
+
+
+  @patch( 'ycmd.completers.cs.cs_completer.FindExecutableWithFallback',
+          wraps = lambda x, fb: x if x == 'omnisharp.exe' else None )
+  @patch( 'os.path.isfile', return_value = True )
+  def test_GetCompleter_CustomOmniSharpFound_MonoRequiredAndMissing( self,
+                                                                     *args ):
+    user_options = user_options_store.GetAll().copy(
+        roslyn_binary_path = 'omnisharp.exe' )
+    assert_that( not GetCompleter( user_options ) )
+
+
+  def test_GetCompleter_OmniSharpDefaultOptions( self, *args ):
+    completer = GetCompleter( user_options_store.GetAll() )
+    assert_that( completer._roslyn_path,
+                 equal_to( PATH_TO_OMNISHARP_ROSLYN_BINARY ) )
+
+
+  @patch( 'ycmd.completers.cs.cs_completer.FindExecutableWithFallback',
+          wraps = lambda x, fb: x if x == 'omnisharp' else None )
+  @patch( 'os.path.isfile', return_value = True )
+  def test_GetCompleter_OmniSharpFromUserOption_NoMonoNeeded( self, *args ):
+    user_options = user_options_store.GetAll().copy(
+        roslyn_binary_path = 'omnisharp' )
+    completer = GetCompleter( user_options )
+    assert_that( completer._roslyn_path, equal_to( 'omnisharp' ) )
+    assert_that( completer.GetCommandLine()[ 0 ], equal_to( 'omnisharp' ) )
+
+
+  @patch( 'ycmd.completers.cs.cs_completer.FindExecutableWithFallback',
+          wraps = lambda x, fb: x if x in ( 'omnisharp.exe',
+                                            'mono' ) else None )
+  @patch( 'os.path.isfile', return_value = True )
+  def test_GetCompleter_OmniSharpFromUserOption_MonoNeeded( self, *args ):
+    user_options = user_options_store.GetAll().copy(
+        roslyn_binary_path = 'omnisharp.exe',
+        mono_binary_path = 'mono' )
+    completer = GetCompleter( user_options )
+    assert_that( completer._roslyn_path, equal_to( 'omnisharp.exe' ) )
+    assert_that( completer._mono, equal_to( 'mono' ) )
+    assert_that( completer.GetCommandLine()[ 0 ], equal_to( 'mono' ) )
diff --git a/ycmd/tests/cs/debug_info_test.py b/ycmd/tests/cs/debug_info_test.py
index f1a4b6fa8b..9e148ec801 100644
--- a/ycmd/tests/cs/debug_info_test.py
+++ b/ycmd/tests/cs/debug_info_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2021 ycmd contributors
+# Copyright (C) 2024 ycmd contributors
 #
 # This file is part of ycmd.
 #
@@ -15,217 +15,55 @@
 # You should have received a copy of the GNU General Public License
 # along with ycmd.  If not, see .
 
-from hamcrest import ( assert_that, contains_exactly, empty, equal_to,
-                       has_entries, has_entry, instance_of )
-
-from subprocess import Popen as _mockable_popen
-
-from unittest.mock import patch
+from hamcrest import ( assert_that,
+                       contains_exactly,
+                       has_entries,
+                       has_entry,
+                       has_items,
+                       instance_of )
 from unittest import TestCase
 
-from ycmd.completers.cs.cs_completer import PATH_TO_OMNISHARP_ROSLYN_BINARY
-from ycmd.completers.cs.hook import GetCompleter
 from ycmd.tests.cs import setUpModule, tearDownModule # noqa
-from ycmd.tests.cs import ( PathToTestFile,
-                            SharedYcmd,
-                            IsolatedYcmd,
-                            WrapOmniSharpServer )
-from ycmd.tests.test_utils import ( BuildRequest,
-                                    WaitUntilCompleterServerReady )
-from ycmd import user_options_store
-from ycmd.utils import ReadFile
-
-
-def SolutionSelectCheck( app, sourcefile, reference_solution,
-                         extra_conf_store = None ):
-  # reusable test: verify that the correct solution (reference_solution) is
-  #   detected for a given source file (and optionally a given extra_conf)
-  if extra_conf_store:
-    app.post_json( '/load_extra_conf_file',
-                   { 'filepath': extra_conf_store } )
-
-  result = app.post_json( '/debug_info',
-                          BuildRequest( completer_target = 'filetype_default',
-                                        filepath = sourcefile,
-                                        filetype = 'cs' ) ).json
-
-  assert_that(
-    result,
-    has_entry( 'completer', has_entries( {
-      'name': 'C#',
-      'servers': contains_exactly( has_entries( {
-        'extras': contains_exactly( has_entries( {
-          'key': 'solution',
-          'value': reference_solution
-        } ) )
-      } ) )
-    } ) )
-  )
+from ycmd.tests.cs import PathToTestFile, SharedYcmd
+from ycmd.tests.test_utils import BuildRequest, is_json_string_matching
 
 
 class DebugInfoTest( TestCase ):
   @SharedYcmd
-  def test_DebugInfo_ServerIsRunning( self, app ):
-    filepath = PathToTestFile( 'testy', 'Program.cs' )
-    contents = ReadFile( filepath )
-    event_data = BuildRequest( filepath = filepath,
-                               filetype = 'cs',
-                               contents = contents,
-                               event_name = 'FileReadyToParse' )
-
-    app.post_json( '/event_notification', event_data )
-    WaitUntilCompleterServerReady( app, 'cs' )
-
-    request_data = BuildRequest( filepath = filepath,
-                                 filetype = 'cs' )
-    assert_that(
-      app.post_json( '/debug_info', request_data ).json,
-      has_entry( 'completer', has_entries( {
-        'name': 'C#',
-        'servers': contains_exactly( has_entries( {
-          'name': 'OmniSharp',
-          'is_running': True,
-          'executable': instance_of( str ),
-          'pid': instance_of( int ),
-          'address': instance_of( str ),
-          'port': instance_of( int ),
-          'logfiles': contains_exactly( instance_of( str ),
-                                instance_of( str ) ),
-          'extras': contains_exactly( has_entries( {
-            'key': 'solution',
-            'value': instance_of( str )
-          } ) )
-        } ) ),
-        'items': empty()
-      } ) )
-    )
-
-
-  @SharedYcmd
-  def test_DebugInfo_ServerIsNotRunning_NoSolution( self, app ):
+  def test_DebugInfo( self, app ):
     request_data = BuildRequest( filetype = 'cs' )
     assert_that(
       app.post_json( '/debug_info', request_data ).json,
       has_entry( 'completer', has_entries( {
-        'name': 'C#',
+        'name': 'Csharp',
         'servers': contains_exactly( has_entries( {
-          'name': 'OmniSharp',
-          'is_running': False,
-          'executable': instance_of( str ),
-          'pid': None,
+          'name': 'OmniSharp-Roslyn',
+          'is_running': instance_of( bool ),
+          'executable': contains_exactly( instance_of( str ),
+                                          instance_of( str ),
+                                          instance_of( str ) ),
           'address': None,
           'port': None,
-          'logfiles': empty()
+          'pid': instance_of( int ),
+          'logfiles': contains_exactly( instance_of( str ) ),
+          'extras': contains_exactly(
+            has_entries( {
+              'key': 'Server State',
+              'value': instance_of( str ),
+            } ),
+            has_entries( {
+              'key': 'Project Directory',
+              'value': PathToTestFile(),
+            } ),
+            has_entries( {
+              'key': 'Open Workspaces',
+              'value': has_items()
+            } ),
+            has_entries( {
+              'key': 'Settings',
+              'value': is_json_string_matching( has_entries( {} ) ),
+            } ),
+          )
         } ) ),
-        'items': empty()
       } ) )
     )
-
-
-  @SharedYcmd
-  def test_DebugInfo_UsesSubfolderHint( self, app ):
-    SolutionSelectCheck( app,
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-named-like-folder',
-                                         'testy', 'Program.cs' ),
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-named-like-folder',
-                                         'testy.sln' ) )
-
-
-  @SharedYcmd
-  def test_DebugInfo_UsesSuperfolderHint( self, app ):
-    SolutionSelectCheck( app,
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-named-like-folder',
-                                         'not-testy', 'Program.cs' ),
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-named-like-folder',
-                                         'solution-named-like-folder.sln' ) )
-
-
-  @SharedYcmd
-  def test_DebugInfo_ExtraConfStoreAbsolute( self, app ):
-    SolutionSelectCheck( app,
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-not-named-like-folder',
-                                         'extra-conf-abs',
-                                         'testy', 'Program.cs' ),
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-not-named-like-folder',
-                                         'testy2.sln' ),
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-not-named-like-folder',
-                                         'extra-conf-abs',
-                                         '.ycm_extra_conf.py' ) )
-
-
-  @SharedYcmd
-  def test_DebugInfo_ExtraConfStoreRelative( self, app ):
-    SolutionSelectCheck( app,
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-not-named-like-folder',
-                                         'extra-conf-rel',
-                                         'testy', 'Program.cs' ),
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-not-named-like-folder',
-                                         'extra-conf-rel',
-                                         'testy2.sln' ),
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-not-named-like-folder',
-                                         'extra-conf-rel',
-                                         '.ycm_extra_conf.py' ) )
-
-
-  @SharedYcmd
-  def test_DebugInfo_ExtraConfStoreNonexisting( self, app ):
-    SolutionSelectCheck( app,
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-not-named-like-folder',
-                                         'extra-conf-bad',
-                                         'testy', 'Program.cs' ),
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-not-named-like-folder',
-                                         'extra-conf-bad',
-                                         'testy2.sln' ),
-                         PathToTestFile( 'testy-multiple-solutions',
-                                         'solution-not-named-like-folder',
-                                         'extra-conf-bad',
-                                         'testy', '.ycm_extra_conf.py' ) )
-
-
-  def test_GetCompleter_RoslynFound( self ):
-    assert_that( GetCompleter( user_options_store.GetAll() ) )
-
-
-  @patch( 'ycmd.completers.cs.cs_completer.PATH_TO_OMNISHARP_ROSLYN_BINARY',
-          None )
-  def test_GetCompleter_RoslynNotFound( *args ):
-    assert_that( not GetCompleter( user_options_store.GetAll() ) )
-
-
-  @patch( 'os.path.isfile', return_value = True )
-  @IsolatedYcmd( { 'roslyn_binary_path': 'my_roslyn.exe' } )
-  def test_GetCompleter_RoslynFromUserOption( self, app, *args ):
-    # `@patch` does not play nice with functions defined at class scope
-    def _popen_mock( cmdline, **kwargs ):
-      exe_index = 1 if cmdline[ 0 ].endswith( 'mono' ) else 0
-      assert_that( cmdline[ exe_index ], equal_to( 'my_roslyn.exe' ) )
-      # Need to redirect to real binary to allow test to pass
-      cmdline[ exe_index ] = PATH_TO_OMNISHARP_ROSLYN_BINARY
-      return _mockable_popen( cmdline, **kwargs )
-
-    filepath = PathToTestFile( 'testy', 'Program.cs' )
-    with patch( 'subprocess.Popen', wraps = _popen_mock ) as popen_mock:
-      with WrapOmniSharpServer( app, filepath ):
-        request = BuildRequest( filepath = filepath, filetype = 'cs' )
-        app.post_json( '/debug_info', request )
-
-    popen_mock.assert_called()
-
-
-  @patch( 'os.path.isfile', return_value = False )
-  def test_GetCompleter_CustomPathToServer_NotAFile( self, *args ):
-    user_options = user_options_store.GetAll().copy(
-      roslyn_binary_path = 'does-not-exist' )
-    assert_that( not GetCompleter( user_options ) )
diff --git a/ycmd/tests/cs/diagnostics_test.py b/ycmd/tests/cs/diagnostics_test.py
index 9f2fa84700..61cf1f471c 100644
--- a/ycmd/tests/cs/diagnostics_test.py
+++ b/ycmd/tests/cs/diagnostics_test.py
@@ -15,18 +15,13 @@
 # You should have received a copy of the GNU General Public License
 # along with ycmd.  If not, see .
 
-from hamcrest import ( assert_that, contains_exactly, contains_string, equal_to,
-                       has_entries, has_entry, has_items )
+from hamcrest import assert_that, has_entry
 from unittest import TestCase
 
 from ycmd.tests.cs import setUpModule, tearDownModule # noqa
-from ycmd.tests.cs import ( IsolatedYcmd,
-                            PathToTestFile,
-                            SharedYcmd,
-                            WrapOmniSharpServer )
+from ycmd.tests.cs import PathToTestFile, SharedYcmd
 from ycmd.tests.test_utils import ( BuildRequest,
-                                    LocationMatcher,
-                                    RangeMatcher,
+                                    WaitForDiagnosticsToBeReady,
                                     WithRetry )
 from ycmd.utils import ReadFile
 
@@ -34,133 +29,18 @@
 class DiagnosticsTest( TestCase ):
   @WithRetry()
   @SharedYcmd
-  def test_Diagnostics_Basic( self, app ):
+  def test_Diagnostics_DetailedDiags( self, app ):
     filepath = PathToTestFile( 'testy', 'Program.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      event_data = BuildRequest( filepath = filepath,
-                                 event_name = 'FileReadyToParse',
-                                 filetype = 'cs',
-                                 contents = contents )
-      app.post_json( '/event_notification', event_data )
-
-      diag_data = BuildRequest( filepath = filepath,
-                                filetype = 'cs',
-                                contents = contents,
-                                line_num = 10,
-                                column_num = 2 )
-
-      results = app.post_json( '/detailed_diagnostic', diag_data ).json
-      assert_that( results,
-                   has_entry(
-                       'message',
-                       contains_string(
-                         "Identifier expected" ) ) )
-
-
-  @SharedYcmd
-  def test_Diagnostics_ZeroBasedLineAndColumn( self, app ):
-    filepath = PathToTestFile( 'testy', 'Program.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      event_data = BuildRequest( filepath = filepath,
-                                 event_name = 'FileReadyToParse',
-                                 filetype = 'cs',
-                                 contents = contents )
-
-      results = app.post_json( '/event_notification', event_data ).json
-
-      assert_that( results, has_items(
-        has_entries( {
-          'kind': equal_to( 'ERROR' ),
-          'text': contains_string( "Identifier expected" ),
-          'location': LocationMatcher( filepath, 10, 12 ),
-          'location_extent': RangeMatcher( filepath, ( 10, 12 ), ( 10, 12 ) ),
-        } )
-      ) )
-
-
-  @WithRetry()
-  @SharedYcmd
-  def test_Diagnostics_WithRange( self, app ):
-    filepath = PathToTestFile( 'testy', 'DiagnosticRange.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      event_data = BuildRequest( filepath = filepath,
-                                 event_name = 'FileReadyToParse',
-                                 filetype = 'cs',
-                                 contents = contents )
-
-      results = app.post_json( '/event_notification', event_data ).json
-
-      assert_that( results, contains_exactly(
-        has_entries( {
-          'kind': equal_to( 'WARNING' ),
-          'text': contains_string(
-            "The variable '\u4e5d' is assigned but its value is never used" ),
-          'location': LocationMatcher( filepath, 6, 13 ),
-          'location_extent': RangeMatcher( filepath, ( 6, 13 ), ( 6, 16 ) )
-        } )
-      ) )
-
-
-  @IsolatedYcmd()
-  def test_Diagnostics_MultipleSolution( self, app ):
-    filepaths = [ PathToTestFile( 'testy', 'Program.cs' ),
-                  PathToTestFile( 'testy-multiple-solutions',
-                                  'solution-named-like-folder',
-                                  'testy', 'Program.cs' ) ]
-    for filepath in filepaths:
-      with WrapOmniSharpServer( app, filepath ):
-        contents = ReadFile( filepath )
-        event_data = BuildRequest( filepath = filepath,
-                                   event_name = 'FileReadyToParse',
-                                   filetype = 'cs',
-                                   contents = contents )
-
-        results = app.post_json( '/event_notification', event_data ).json
-        assert_that( results, has_items(
-          has_entries( {
-            'kind': equal_to( 'ERROR' ),
-            'text': contains_string( "Identifier expected" ),
-            'location': LocationMatcher( filepath, 10, 12 ),
-            'location_extent': RangeMatcher(
-                filepath, ( 10, 12 ), ( 10, 12 ) )
-          } )
-        ) )
-
-
-  @IsolatedYcmd( { 'max_diagnostics_to_display': 1 } )
-  def test_Diagnostics_MaximumDiagnosticsNumberExceeded( self, app ):
-    filepath = PathToTestFile( 'testy', 'MaxDiagnostics.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      event_data = BuildRequest( filepath = filepath,
-                                 event_name = 'FileReadyToParse',
+    contents = ReadFile( filepath )
+    WaitForDiagnosticsToBeReady( app, filepath, contents, 'cs' )
+    request_data = BuildRequest( contents = contents,
+                                 filepath = filepath,
                                  filetype = 'cs',
-                                 contents = contents )
-
-      results = app.post_json( '/event_notification', event_data ).json
-
-      assert_that( results, contains_exactly(
-        has_entries( {
-          'kind': equal_to( 'ERROR' ),
-          'text': contains_string( "The type 'MaxDiagnostics' already contains "
-                                   "a definition for 'test'" ),
-          'location': LocationMatcher( filepath, 4, 16 ),
-          'location_extent': RangeMatcher( filepath, ( 4, 16 ), ( 4, 20 ) )
-        } ),
-        has_entries( {
-          'kind': equal_to( 'ERROR' ),
-          'text': contains_string( 'Maximum number of diagnostics exceeded.' ),
-          'location': LocationMatcher( filepath, 1, 1 ),
-          'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ),
-          'ranges': contains_exactly(
-            RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) )
-          )
-        } )
-      ) )
+                                 line_num = 11,
+                                 column_num = 1 )
+
+    results = app.post_json( '/detailed_diagnostic', request_data ).json
+    assert_that( results,
+                 has_entry( 'message',
+                            "'Console' does not contain "
+                            "a definition for '' [CS0117]" ) )
diff --git a/ycmd/tests/cs/get_completions_test.py b/ycmd/tests/cs/get_completions_test.py
index a992d96bda..83f7e78d76 100644
--- a/ycmd/tests/cs/get_completions_test.py
+++ b/ycmd/tests/cs/get_completions_test.py
@@ -16,180 +16,44 @@
 # along with ycmd.  If not, see .
 
 from hamcrest import ( assert_that,
-                       calling,
                        empty,
                        has_entries,
-                       has_items,
-                       raises )
+                       has_items )
 from unittest import TestCase
-from webtest import AppError
 
 from ycmd.tests.cs import setUpModule, tearDownModule # noqa
-from ycmd.tests.cs import PathToTestFile, SharedYcmd, WrapOmniSharpServer
-from ycmd.tests.test_utils import BuildRequest, CompletionEntryMatcher
+from ycmd.tests.cs import PathToTestFile, SharedYcmd
+from ycmd.tests.test_utils import ( BuildRequest,
+                                    CompletionEntryMatcher,
+                                    WithRetry )
 from ycmd.utils import ReadFile
 
 
 class GetCompletionsTest( TestCase ):
-  @SharedYcmd
-  def test_GetCompletions_DefaultToIdentifier( self, app ):
-    filepath = PathToTestFile( 'testy', 'Program.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      completion_data = BuildRequest( filepath = filepath,
-                                      filetype = 'cs',
-                                      contents = contents,
-                                      line_num = 10,
-                                      column_num = 7 )
-      response_data = app.post_json( '/completions', completion_data ).json
-      print( 'Response: ', response_data )
-      assert_that(
-        response_data,
-        has_entries( {
-          'completion_start_column': 4,
-          'completions': has_items(
-            CompletionEntryMatcher( 'Console', '[ID]' ),
-          ),
-          'errors': empty(),
-        } ) )
-
-
+  @WithRetry
   @SharedYcmd
   def test_GetCompletions_Basic( self, app ):
     filepath = PathToTestFile( 'testy', 'Program.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      completion_data = BuildRequest( filepath = filepath,
-                                      filetype = 'cs',
-                                      contents = contents,
-                                      line_num = 10,
-                                      column_num = 12 )
-      response_data = app.post_json( '/completions', completion_data ).json
-      print( 'Response: ', response_data )
-      assert_that(
-        response_data,
-        has_entries( {
-          'completion_start_column': 12,
-          'completions': has_items(
-            CompletionEntryMatcher( 'CursorLeft',
-                                    'CursorLeft',
-                                    { 'kind': 'Property' } ),
-            CompletionEntryMatcher( 'CursorSize',
-                                    'CursorSize',
-                                    { 'kind': 'Property' } ),
-          ),
-          'errors': empty(),
-        } ) )
-
-
-  @SharedYcmd
-  def test_GetCompletions_Unicode( self, app ):
-    filepath = PathToTestFile( 'testy', 'Unicode.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      completion_data = BuildRequest( filepath = filepath,
-                                      filetype = 'cs',
-                                      contents = contents,
-                                      line_num = 43,
-                                      column_num = 26 )
-      response_data = app.post_json( '/completions', completion_data ).json
-      assert_that( response_data,
-                   has_entries( {
-                      'completion_start_column': 26,
-                      'completions': has_items(
-                        CompletionEntryMatcher( 'DoATest' ),
-                        CompletionEntryMatcher( 'an_int' ),
-                        CompletionEntryMatcher( 'a_unicøde' ),
-                        CompletionEntryMatcher( 'øøø' ),
-                      ),
-                      'errors': empty(),
-                    } ) )
-
-
-  @SharedYcmd
-  def test_GetCompletions_MultipleSolution( self, app ):
-    filepaths = [ PathToTestFile( 'testy', 'Program.cs' ),
-                  PathToTestFile( 'testy-multiple-solutions',
-                                  'solution-named-like-folder',
-                                  'testy',
-                                  'Program.cs' ) ]
-    for filepath in filepaths:
-      with WrapOmniSharpServer( app, filepath ):
-        contents = ReadFile( filepath )
-
-        completion_data = BuildRequest( filepath = filepath,
-                                        filetype = 'cs',
-                                        contents = contents,
-                                        line_num = 10,
-                                        column_num = 12 )
-        response_data = app.post_json( '/completions',
-                                       completion_data ).json
-
-        print( 'Response: ', response_data )
-        assert_that(
-          response_data,
-          has_entries( {
-            'completion_start_column': 12,
-            'completions': has_items(
-              CompletionEntryMatcher( 'CursorLeft',
-                                      'CursorLeft',
-                                      { 'kind': 'Property' } ),
-              CompletionEntryMatcher( 'CursorSize',
-                                      'CursorSize',
-                                      { 'kind': 'Property' } ),
-            ),
-            'errors': empty(),
-          } ) )
-
-
-  @SharedYcmd
-  def test_GetCompletions_PathWithSpace( self, app ):
-    filepath = PathToTestFile( 'неприличное слово', 'Program.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      completion_data = BuildRequest( filepath = filepath,
-                                      filetype = 'cs',
-                                      contents = contents,
-                                      line_num = 9,
-                                      column_num = 12 )
-      response_data = app.post_json( '/completions', completion_data ).json
-      print( 'Response: ', response_data )
-      assert_that(
-        response_data,
-        has_entries( {
-          'completion_start_column': 12,
-          'completions': has_items(
-            CompletionEntryMatcher( 'CursorLeft',
-                                    'CursorLeft',
-                                    { 'kind': 'Property' } ),
-            CompletionEntryMatcher( 'CursorSize',
-                                    'CursorSize',
-                                    { 'kind': 'Property' } ),
-          ),
-          'errors': empty(),
-        } ) )
-
-
-  @SharedYcmd
-  def test_GetCompletions_DoesntStartWithAmbiguousMultipleSolutions(
-      self, app ):
-    filepath = PathToTestFile( 'testy-multiple-solutions',
-                               'solution-not-named-like-folder',
-                               'testy', 'Program.cs' )
     contents = ReadFile( filepath )
-    event_data = BuildRequest( filepath = filepath,
-                               filetype = 'cs',
-                               contents = contents,
-                               event_name = 'FileReadyToParse' )
 
+    completion_data = BuildRequest( filepath = filepath,
+                                    filetype = 'cs',
+                                    contents = contents,
+                                    line_num = 10,
+                                    column_num = 12 )
+    response_data = app.post_json( '/completions', completion_data ).json
+    print( 'Response: ', response_data )
     assert_that(
-      calling( app.post_json ).with_args( '/event_notification', event_data ),
-      raises( AppError, 'Autodetection of solution file failed' ),
-      "The Omnisharp server started, despite us not being able to find a "
-      "suitable solution file to feed it. Did you fiddle with the solution "
-      "finding code in cs_completer.py? Hopefully you've enhanced it: you need "
-      "to update this test then :)" )
+      response_data,
+      has_entries( {
+        'completion_start_column': 12,
+        'completions': has_items(
+          CompletionEntryMatcher( 'CursorLeft',
+                                  None,
+                                  { 'kind': 'Property' } ),
+          CompletionEntryMatcher( 'CursorSize',
+                                  None,
+                                  { 'kind': 'Property' } ),
+        ),
+        'errors': empty(),
+      } ) )
diff --git a/ycmd/tests/cs/server_management_test.py b/ycmd/tests/cs/server_management_test.py
new file mode 100644
index 0000000000..837a2387fa
--- /dev/null
+++ b/ycmd/tests/cs/server_management_test.py
@@ -0,0 +1,138 @@
+# Copyright (C) 2024 ycmd contributors
+#
+# This file is part of ycmd.
+#
+# ycmd is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ycmd is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ycmd.  If not, see .
+
+from hamcrest import assert_that, contains_exactly, equal_to, has_entry
+from unittest.mock import patch
+from unittest import TestCase
+
+from ycmd.completers.language_server.language_server_completer import (
+    LanguageServerConnectionTimeout )
+from ycmd.tests.cs import ( PathToTestFile,
+                            IsolatedYcmd,
+                            StartCsCompleterServerInDirectory )
+from ycmd.tests.test_utils import ( BuildRequest,
+                                    MockProcessTerminationTimingOut,
+                                    WaitUntilCompleterServerReady )
+
+
+def AssertCsCompleterServerIsRunning( app, is_running ):
+  request_data = BuildRequest( filetype = 'cs' )
+  assert_that( app.post_json( '/debug_info', request_data ).json,
+               has_entry(
+                 'completer',
+                 has_entry( 'servers', contains_exactly(
+                   has_entry( 'is_running', is_running )
+                 ) )
+               ) )
+
+
+class ServerManagementTest( TestCase ):
+  @IsolatedYcmd()
+  def test_ServerManagement_RestartServer( self, app ):
+    filepath = PathToTestFile( 'Program.cs' )
+    StartCsCompleterServerInDirectory( app, filepath )
+
+    AssertCsCompleterServerIsRunning( app, True )
+
+    app.post_json(
+      '/run_completer_command',
+      BuildRequest(
+        filepath = filepath,
+        filetype = 'cs',
+        command_arguments = [ 'RestartServer' ],
+      ),
+    )
+
+    WaitUntilCompleterServerReady( app, 'cs' )
+
+    AssertCsCompleterServerIsRunning( app, True )
+
+
+  @IsolatedYcmd()
+  @patch( 'shutil.rmtree', side_effect = OSError )
+  @patch( 'ycmd.utils.WaitUntilProcessIsTerminated',
+          MockProcessTerminationTimingOut )
+  def test_ServerManagement_CloseServer_Unclean( self, app, *args ):
+    StartCsCompleterServerInDirectory( app, PathToTestFile() )
+
+    app.post_json(
+      '/run_completer_command',
+      BuildRequest(
+        filetype = 'cs',
+        command_arguments = [ 'StopServer' ]
+      )
+    )
+
+    request_data = BuildRequest( filetype = 'cs' )
+    assert_that( app.post_json( '/debug_info', request_data ).json,
+                 has_entry(
+                   'completer',
+                   has_entry( 'servers', contains_exactly(
+                     has_entry( 'is_running', False )
+                   ) )
+                 ) )
+
+
+  @IsolatedYcmd()
+  def test_ServerManagement_StopServerTwice( self, app ):
+    StartCsCompleterServerInDirectory( app, PathToTestFile() )
+
+    app.post_json(
+      '/run_completer_command',
+      BuildRequest(
+        filetype = 'cs',
+        command_arguments = [ 'StopServer' ],
+      ),
+    )
+
+    AssertCsCompleterServerIsRunning( app, False )
+
+    # Stopping a stopped server is a no-op
+    app.post_json(
+      '/run_completer_command',
+      BuildRequest(
+        filetype = 'cs',
+        command_arguments = [ 'StopServer' ],
+      ),
+    )
+
+    AssertCsCompleterServerIsRunning( app, False )
+
+
+  @IsolatedYcmd
+  def test_ServerManagement_StartServer_Fails( self, app ):
+    with patch( 'ycmd.completers.language_server.language_server_completer.'
+                'LanguageServerConnection.AwaitServerConnection',
+                side_effect = LanguageServerConnectionTimeout ):
+      resp = app.post_json( '/event_notification',
+                     BuildRequest(
+                       event_name = 'FileReadyToParse',
+                       filetype = 'cs',
+                       filepath = PathToTestFile( 'Program.cs' ),
+                       contents = ""
+                     ) )
+
+      assert_that( resp.status_code, equal_to( 200 ) )
+
+      request_data = BuildRequest( filetype = 'cs' )
+      assert_that( app.post_json( '/debug_info', request_data ).json,
+                   has_entry(
+                     'completer',
+                     has_entry( 'servers', contains_exactly(
+                       has_entry( 'is_running', False )
+                     ) )
+                   ) )
diff --git a/ycmd/tests/cs/signature_help_test.py b/ycmd/tests/cs/signature_help_test.py
index b7c310fbdf..c6213c1456 100644
--- a/ycmd/tests/cs/signature_help_test.py
+++ b/ycmd/tests/cs/signature_help_test.py
@@ -18,8 +18,7 @@
 from hamcrest import ( assert_that,
                        contains_exactly,
                        empty,
-                       has_entries,
-                       has_items )
+                       has_entries )
 from unittest.mock import patch
 from unittest import TestCase
 from ycmd import handlers
@@ -27,13 +26,11 @@
 from ycmd.tests.cs import setUpModule, tearDownModule # noqa
 from ycmd.tests.cs import ( PathToTestFile, # noqa
                             IsolatedYcmd,
-                            SharedYcmd,
-                            WrapOmniSharpServer )
+                            SharedYcmd )
 from ycmd.tests.test_utils import ( BuildRequest,
                                     ParameterMatcher,
                                     SignatureMatcher,
-                                    SignatureAvailableMatcher,
-                                    CompletionEntryMatcher )
+                                    SignatureAvailableMatcher )
 
 
 class SignatureHelpTest( TestCase ):
@@ -63,21 +60,20 @@ def test_SignatureHelp_TriggerComma( self, app ):
       filetypes = [ 'cs' ],
       filepath = filepath,
       contents = contents )
-    with WrapOmniSharpServer( app, filepath ):
-      response = app.post_json( '/signature_help', request ).json
-      LOGGER.debug( 'response = %s', response )
-      assert_that( response, has_entries( {
-        'errors': empty(),
-        'signature_help': has_entries( {
-          'activeSignature': 0,
-          'activeParameter': 1,
-          'signatures': contains_exactly(
-            SignatureMatcher( 'void ContinuousTest.MultiArg(int i, string s)',
-                              [ ParameterMatcher( 29, 34 ),
-                                ParameterMatcher( 36, 44 ) ] )
-          )
-        } )
-      } ) )
+    response = app.post_json( '/signature_help', request ).json
+    LOGGER.debug( 'response = %s', response )
+    assert_that( response, has_entries( {
+      'errors': empty(),
+      'signature_help': has_entries( {
+        'activeSignature': 0,
+        'activeParameter': 1,
+        'signatures': contains_exactly(
+          SignatureMatcher( 'void ContinuousTest.MultiArg(int i, string s)',
+                            [ ParameterMatcher( 29, 34 ),
+                              ParameterMatcher( 36, 44 ) ] )
+        )
+      } )
+    } ) )
 
 
   @SharedYcmd
@@ -90,20 +86,19 @@ def test_SignatureHelp_TriggerParen( self, app ):
       filetypes = [ 'cs' ],
       filepath = filepath,
       contents = contents )
-    with WrapOmniSharpServer( app, filepath ):
-      response = app.post_json( '/signature_help', request ).json
-      LOGGER.debug( 'response = %s', response )
-      assert_that( response, has_entries( {
-        'errors': empty(),
-        'signature_help': has_entries( {
-          'activeSignature': 0,
-          'activeParameter': 0,
-          'signatures': contains_exactly(
-            SignatureMatcher( 'void ContinuousTest.Main(string[] args)',
-                              [ ParameterMatcher( 25, 38 ) ] )
-          )
-        } )
-      } ) )
+    response = app.post_json( '/signature_help', request ).json
+    LOGGER.debug( 'response = %s', response )
+    assert_that( response, has_entries( {
+      'errors': empty(),
+      'signature_help': has_entries( {
+        'activeSignature': 0,
+        'activeParameter': 0,
+        'signatures': contains_exactly(
+          SignatureMatcher( 'void ContinuousTest.Main(string[] args)',
+                            [ ParameterMatcher( 25, 38 ) ] )
+        )
+      } )
+    } ) )
 
 
   @IsolatedYcmd( { 'disable_signature_help': True } )
@@ -116,17 +111,16 @@ def test_SignatureHelp_TriggerParen_Disabled( self, app ):
       filetypes = [ 'cs' ],
       filepath = filepath,
       contents = contents )
-    with WrapOmniSharpServer( app, filepath ):
-      response = app.post_json( '/signature_help', request ).json
-      LOGGER.debug( 'response = %s', response )
-      assert_that( response, has_entries( {
-        'errors': empty(),
-        'signature_help': has_entries( {
-          'activeSignature': 0,
-          'activeParameter': 0,
-          'signatures': empty()
-        } )
-      } ) )
+    response = app.post_json( '/signature_help', request ).json
+    LOGGER.debug( 'response = %s', response )
+    assert_that( response, has_entries( {
+      'errors': empty(),
+      'signature_help': has_entries( {
+        'activeSignature': 0,
+        'activeParameter': 0,
+        'signatures': empty()
+      } )
+    } ) )
 
 
   @SharedYcmd
@@ -139,41 +133,39 @@ def test_SignatureHelp_MultipleSignatures( self, app ):
       filetypes = [ 'cs' ],
       filepath = filepath,
       contents = contents )
-    with WrapOmniSharpServer( app, filepath ):
-      response = app.post_json( '/signature_help', request ).json
-      LOGGER.debug( 'response = %s', response )
-      assert_that( response, has_entries( {
-        'errors': empty(),
-        'signature_help': has_entries( {
-          'activeSignature': 0,
-          'activeParameter': 0,
-          'signatures': contains_exactly(
-            SignatureMatcher( 'void ContinuousTest.Overloaded(int i, int a)',
-                              [ ParameterMatcher( 31, 36 ),
-                                ParameterMatcher( 38, 43 ) ] ),
-            SignatureMatcher( 'void ContinuousTest.Overloaded(string s)',
-                              [ ParameterMatcher( 31, 39 ) ] ),
-          )
-        } )
-      } ) )
+    response = app.post_json( '/signature_help', request ).json
+    LOGGER.debug( 'response = %s', response )
+    assert_that( response, has_entries( {
+      'errors': empty(),
+      'signature_help': has_entries( {
+        'activeSignature': 0,
+        'activeParameter': 0,
+        'signatures': contains_exactly(
+          SignatureMatcher( 'void ContinuousTest.Overloaded(int i, int a)',
+                            [ ParameterMatcher( 31, 36 ),
+                              ParameterMatcher( 38, 43 ) ] ),
+          SignatureMatcher( 'void ContinuousTest.Overloaded(string s)',
+                            [ ParameterMatcher( 31, 39 ) ] ),
+        )
+      } )
+    } ) )
     request[ 'column_num' ] = 20
-    with WrapOmniSharpServer( app, filepath ):
-      response = app.post_json( '/signature_help', request ).json
-      LOGGER.debug( 'response = %s', response )
-      assert_that( response, has_entries( {
-        'errors': empty(),
-        'signature_help': has_entries( {
-          'activeSignature': 0,
-          'activeParameter': 1,
-          'signatures': contains_exactly(
-            SignatureMatcher( 'void ContinuousTest.Overloaded(int i, int a)',
-                              [ ParameterMatcher( 31, 36 ),
-                                ParameterMatcher( 38, 43 ) ] ),
-            SignatureMatcher( 'void ContinuousTest.Overloaded(string s)',
-                              [ ParameterMatcher( 31, 39 ) ] ),
-          )
-        } )
-      } ) )
+    response = app.post_json( '/signature_help', request ).json
+    LOGGER.debug( 'response = %s', response )
+    assert_that( response, has_entries( {
+      'errors': empty(),
+      'signature_help': has_entries( {
+        'activeSignature': 0,
+        'activeParameter': 1,
+        'signatures': contains_exactly(
+          SignatureMatcher( 'void ContinuousTest.Overloaded(int i, int a)',
+                            [ ParameterMatcher( 31, 36 ),
+                              ParameterMatcher( 38, 43 ) ] ),
+          SignatureMatcher( 'void ContinuousTest.Overloaded(string s)',
+                            [ ParameterMatcher( 31, 39 ) ] ),
+        )
+      } )
+    } ) )
 
 
   @SharedYcmd
@@ -186,43 +178,13 @@ def test_SignatureHelp_NotAFunction_NoError( self, app ):
       filetypes = [ 'cs' ],
       filepath = filepath,
       contents = contents )
-    with WrapOmniSharpServer( app, filepath ):
-      response = app.post_json( '/signature_help', request ).json
-      LOGGER.debug( 'response = %s', response )
-      assert_that( response, has_entries( {
-        'errors': empty(),
-        'signature_help': has_entries( {
-          'activeSignature': 0,
-          'activeParameter': 0,
-          'signatures': empty()
-        } )
-      } ) )
-
-
-  @IsolatedYcmd( { 'disable_signature_help': True } )
-  def test_GetCompletions_Basic_NoSigHelp( self, app ):
-    filepath = PathToTestFile( 'testy', 'Program.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      completion_data = BuildRequest( filepath = filepath,
-                                      filetype = 'cs',
-                                      contents = contents,
-                                      line_num = 10,
-                                      column_num = 12 )
-      response_data = app.post_json( '/completions', completion_data ).json
-      print( 'Response: ', response_data )
-      assert_that(
-        response_data,
-        has_entries( {
-          'completion_start_column': 12,
-          'completions': has_items(
-            CompletionEntryMatcher( 'CursorLeft',
-                                    'CursorLeft',
-                                    { 'kind': 'Property' } ),
-            CompletionEntryMatcher( 'CursorSize',
-                                    'CursorSize',
-                                    { 'kind': 'Property' } ),
-          ),
-          'errors': empty(),
-        } ) )
+    response = app.post_json( '/signature_help', request ).json
+    LOGGER.debug( 'response = %s', response )
+    assert_that( response, has_entries( {
+      'errors': empty(),
+      'signature_help': has_entries( {
+        'activeSignature': 0,
+        'activeParameter': 0,
+        'signatures': empty()
+      } )
+    } ) )
diff --git a/ycmd/tests/cs/subcommands_test.py b/ycmd/tests/cs/subcommands_test.py
index 0f40c91d4a..4a4fba37d1 100644
--- a/ycmd/tests/cs/subcommands_test.py
+++ b/ycmd/tests/cs/subcommands_test.py
@@ -16,6 +16,7 @@
 # along with ycmd.  If not, see .
 
 from hamcrest import ( assert_that,
+                       contains_inanyorder,
                        empty,
                        has_entries,
                        has_entry,
@@ -25,12 +26,11 @@
 from unittest import TestCase
 import os.path
 
-from ycmd import user_options_store
+from ycmd import handlers, user_options_store
 from ycmd.tests.cs import setUpModule, tearDownModule # noqa
 from ycmd.tests.cs import ( IsolatedYcmd,
                             PathToTestFile,
-                            SharedYcmd,
-                            WrapOmniSharpServer )
+                            SharedYcmd )
 from ycmd.tests.test_utils import ( BuildRequest,
                                     ChunkMatcher,
                                     ErrorMatcher,
@@ -39,225 +39,261 @@
                                     RangeMatcher,
                                     WaitUntilCompleterServerReady,
                                     WithRetry )
-from ycmd.utils import ReadFile
+from ycmd.utils import ReadFile, OnWindows
 
 
 def StopServer_KeepLogFiles( app ):
   filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-  with WrapOmniSharpServer( app, filepath ):
-    event_data = BuildRequest( filetype = 'cs', filepath = filepath )
-
-    response = app.post_json( '/debug_info', event_data ).json
-
-    logfiles = []
-    for server in response[ 'completer' ][ 'servers' ]:
-      logfiles.extend( server[ 'logfiles' ] )
-
-    try:
-      for logfile in logfiles:
-        assert_that( os.path.exists( logfile ),
-                     f'Logfile should exist at { logfile }' )
-    finally:
-      app.post_json(
-        '/run_completer_command',
-        BuildRequest(
-          filetype = 'cs',
-          filepath = filepath,
-          command_arguments = [ 'StopServer' ]
-        )
+  event_data = BuildRequest( filetype = 'cs', filepath = filepath )
+
+  response = app.post_json( '/debug_info', event_data ).json
+
+  logfiles = []
+  for server in response[ 'completer' ][ 'servers' ]:
+    logfiles.extend( server[ 'logfiles' ] )
+
+  try:
+    for logfile in logfiles:
+      assert_that( os.path.exists( logfile ),
+                   f'Logfile should exist at { logfile }' )
+  finally:
+    app.post_json(
+      '/run_completer_command',
+      BuildRequest(
+        filetype = 'cs',
+        filepath = filepath,
+        command_arguments = [ 'StopServer' ]
       )
+    )
 
-    if user_options_store.Value( 'server_keep_logfiles' ):
-      for logfile in logfiles:
-        assert_that( os.path.exists( logfile ),
-                     f'Logfile should still exist at { logfile }' )
-    else:
-      for logfile in logfiles:
-        assert_that( not os.path.exists( logfile ),
-                     f'Logfile should no longer exist at { logfile }' )
+  if user_options_store.Value( 'server_keep_logfiles' ):
+    for logfile in logfiles:
+      assert_that( os.path.exists( logfile ),
+                   f'Logfile should still exist at { logfile }' )
+  else:
+    for logfile in logfiles:
+      assert_that( not os.path.exists( logfile ),
+                   f'Logfile should no longer exist at { logfile }' )
 
 
 class SubcommandsTest( TestCase ):
   @SharedYcmd
-  def test_Subcommands_FixIt_NoFixitsFound( self, app ):
-    fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' )
-    with WrapOmniSharpServer( app, fixit_test ):
-      contents = ReadFile( fixit_test )
+  def test_Subcommands_DefinedSubcommands( self, app ):
+    subcommands_data = BuildRequest( completer_target = 'cs' )
+
+    assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json,
+                 contains_inanyorder( 'FixIt',
+                                      'Format',
+                                      'GetDoc',
+                                      'GetType',
+                                      'GoTo',
+                                      'GoToDeclaration',
+                                      'GoToDefinition',
+                                      'GoToDocumentOutline',
+                                      'GoToImplementation',
+                                      'GoToReferences',
+                                      'GoToSymbol',
+                                      'GoToType',
+                                      'RefactorRename',
+                                      'RestartServer' ) )
+
+
+  @SharedYcmd
+  def test_Subcommands_ServerNotInitialized( self, app ):
+    filepath = PathToTestFile( 'testy', 'Program.cs' )
+
+    completer = handlers._server_state.GetFiletypeCompleter( [ 'cs' ] )
 
+    @patch.object( completer, '_ServerIsInitialized', return_value = False )
+    def Test( app, cmd, arguments ):
       request = BuildRequest( completer_target = 'filetype_default',
-                              command_arguments = [ 'FixIt' ],
+                              command_arguments = [ cmd ],
                               line_num = 1,
-                              column_num = 1,
-                              contents = contents,
+                              column_num = 15,
+                              contents = ReadFile( filepath ),
                               filetype = 'cs',
-                              filepath = fixit_test )
-      response = app.post_json( '/run_completer_command', request ).json
-      assert_that( response, has_entries( { 'fixits': empty() } ) )
+                              filepath = filepath )
+      response = app.post_json( '/run_completer_command',
+                                request,
+                                expect_errors = True ).json
+      assert_that( response,
+                   ErrorMatcher( RuntimeError,
+                   'Server is initializing. Please wait.' ) )
+
+    Test( app, 'FixIt' )
+    Test( app, 'Format' )
+    Test( app, 'GetDoc' )
+    Test( app, 'GetType' )
+    Test( app, 'GoTo' )
+    Test( app, 'GoToDeclaration' )
+    Test( app, 'GoToDefinition' )
+    Test( app, 'GoToDocumentOutline' )
+    Test( app, 'GoToImplementation' )
+    Test( app, 'GoToReferences' )
+    Test( app, 'GoToSymbol' )
+    Test( app, 'GoToType' )
+    Test( app, 'RefactorRename' )
 
 
   @SharedYcmd
-  def test_Subcommands_FixIt_Multi( self, app ):
+  def test_Subcommands_FixIt_NoFixitsFound( self, app ):
     fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' )
-    with WrapOmniSharpServer( app, fixit_test ):
-      contents = ReadFile( fixit_test )
+    contents = ReadFile( fixit_test )
 
-      request = BuildRequest( completer_target = 'filetype_default',
-                              command_arguments = [ 'FixIt' ],
-                              line_num = 5,
-                              column_num = 27,
-                              contents = contents,
-                              filetype = 'cs',
-                              filepath = fixit_test )
-      response = app.post_json( '/run_completer_command', request ).json
-      assert_that( response, has_entries( {
-        'fixits': contains_exactly(
-          has_entries( {
-            'text': 'Introduce constant',
-            'command': has_entries( { 'index': 0 } ),
-            'resolve': True } ),
-          has_entries( {
-            'text': 'Convert to binary',
-            'command': has_entries( { 'index': 1 } ),
-            'resolve': True } ),
-          has_entries( {
-            'text': 'Convert to hex',
-            'command': has_entries( { 'index': 2 } ),
-            'resolve': True } ),
-        ) } ) )
-      request.pop( 'command_arguments' )
-      request.update( { 'fixit': response[ 'fixits' ][ 1 ] } )
-      response = app.post_json( '/resolve_fixit', request ).json
-      assert_that( response, has_entries( {
-        'fixits': contains_exactly( has_entries( {
-          'location': LocationMatcher( fixit_test, 5, 27 ),
-          'chunks': contains_exactly(
-            has_entries( { 'replacement_text': '0b101', } ) )
-        } ) ) } ) )
+    request = BuildRequest( completer_target = 'filetype_default',
+                            command_arguments = [ 'FixIt' ],
+                            line_num = 1,
+                            column_num = 1,
+                            contents = contents,
+                            filetype = 'cs',
+                            filepath = fixit_test )
+    response = app.post_json( '/run_completer_command', request ).json
+    assert_that( response, has_entries( { 'fixits': empty() } ) )
 
 
   @SharedYcmd
-  def test_Subcommands_FixIt_Range( self, app ):
+  def test_Subcommands_FixIt_Multi( self, app ):
     fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' )
-    with WrapOmniSharpServer( app, fixit_test ):
-      contents = ReadFile( fixit_test )
-
-      request = BuildRequest( completer_target = 'filetype_default',
-                              command_arguments = [ 'FixIt' ],
-                              line_num = 5,
-                              column_num = 23,
-                              contents = contents,
-                              filetype = 'cs',
-                              filepath = fixit_test )
-      request.update( { 'range': {
-        'start': { 'line_num': 5, 'column_num': 23 },
-        'end': { 'line_num': 5, 'column_num': 27 }
-      } } )
-      response = app.post_json( '/run_completer_command', request ).json
-      assert_that( response, has_entries( {
-        'fixits': contains_exactly(
-          has_entries( {
-            'text': 'Extract method',
-            'command': has_entries( { 'index': 0 } ),
-            'resolve': True } ),
-          has_entries( {
-            'text': 'Extract local function',
-            'command': has_entries( { 'index': 1 } ),
-            'resolve': True } ),
-        )
-      } ) )
+    contents = ReadFile( fixit_test )
+
+    request = BuildRequest( completer_target = 'filetype_default',
+                            command_arguments = [ 'FixIt' ],
+                            line_num = 5,
+                            column_num = 27,
+                            contents = contents,
+                            filetype = 'cs',
+                            filepath = fixit_test )
+    response = app.post_json( '/run_completer_command', request ).json
+    assert_that( response, has_entries( {
+      'fixits': contains_exactly(
+        has_entries( {
+          'text': 'Extract method',
+          'resolve': True } ),
+        has_entries( {
+          'text': 'Extract local function',
+          'resolve': True } ),
+        has_entries( {
+          'text': 'Introduce parameter for \'5\' '
+                  '-> and update call sites directly',
+          'resolve': True } ),
+        has_entries( {
+          'text': 'Introduce parameter for all occurrences of \'5\' '
+                  '-> and update call sites directly',
+          'resolve': True } ),
+        has_entries( {
+          'text': 'Convert number -> Convert to binary',
+          'resolve': True } ),
+        has_entries( {
+          'text': 'Convert number -> Convert to hex',
+          'resolve': True } ),
+      ) } ) )
+    request.pop( 'command_arguments' )
+    request.update( { 'fixit': response[ 'fixits' ][ 4 ] } )
+    response = app.post_json( '/resolve_fixit', request ).json
+    assert_that( response, has_entries( {
+      'fixits': contains_exactly( has_entries( {
+        'location': LocationMatcher( fixit_test, 5, 27 ),
+        'chunks': contains_exactly(
+          has_entries( { 'replacement_text': '0b101', } ) )
+      } ) ) } ) )
 
 
   @SharedYcmd
-  def test_Subcommands_FixIt_Single( self, app ):
+  def test_Subcommands_FixIt_Range( self, app ):
     fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' )
-    with WrapOmniSharpServer( app, fixit_test ):
-      contents = ReadFile( fixit_test )
-
-      request = BuildRequest( completer_target = 'filetype_default',
-                              command_arguments = [ 'FixIt' ],
-                              line_num = 4,
-                              column_num = 17,
-                              contents = contents,
-                              filetype = 'cs',
-                              filepath = fixit_test )
-      response = app.post_json( '/run_completer_command', request ).json
-      assert_that( response, has_entries( {
-        'fixits': contains_exactly( has_entries( {
-          'location': LocationMatcher( fixit_test, 4, 17 ),
-          'chunks': contains_exactly(
-            has_entries( {
-              'replacement_text': 'var',
-              'range': RangeMatcher( fixit_test, ( 4, 13 ), ( 4, 16 ) ) } )
-          ) } ) ) } ) )
+    contents = ReadFile( fixit_test )
+
+    request = BuildRequest( completer_target = 'filetype_default',
+                            command_arguments = [ 'FixIt' ],
+                            line_num = 5,
+                            column_num = 23,
+                            contents = contents,
+                            filetype = 'cs',
+                            filepath = fixit_test )
+    request.update( { 'range': {
+      'start': { 'line_num': 5, 'column_num': 23 },
+      'end': { 'line_num': 5, 'column_num': 27 }
+    } } )
+    response = app.post_json( '/run_completer_command', request ).json
+    assert_that( response, has_entries( {
+      'fixits': contains_exactly(
+        has_entries( {
+          'text': 'Remove unused variable',
+          'resolve': True } ),
+        has_entries( {
+          'text': 'Extract method',
+          'resolve': True } ),
+        has_entries( {
+          'text': 'Extract local function',
+          'resolve': True } ),
+      )
+    } ) )
 
 
   @SharedYcmd
   def test_Subcommands_RefactorRename_MissingNewName( self, app ):
     continuous_test = PathToTestFile( 'testy', 'ContinuousTest.cs' )
-    with WrapOmniSharpServer( app, continuous_test ):
-      contents = ReadFile( continuous_test )
-
-      request = BuildRequest( completer_target = 'filetype_default',
-                              command_arguments = [ 'RefactorRename' ],
-                              line_num = 5,
-                              column_num = 15,
-                              contents = contents,
-                              filetype = 'cs',
-                              filepath = continuous_test )
-      response = app.post_json( '/run_completer_command',
-                                request,
-                                expect_errors = True ).json
-      assert_that( response, ErrorMatcher( ValueError,
-                              'Please specify a new name to rename it to.\n'
-                              'Usage: RefactorRename ' ) )
+    contents = ReadFile( continuous_test )
+
+    request = BuildRequest( completer_target = 'filetype_default',
+                            command_arguments = [ 'RefactorRename' ],
+                            line_num = 5,
+                            column_num = 15,
+                            contents = contents,
+                            filetype = 'cs',
+                            filepath = continuous_test )
+    response = app.post_json( '/run_completer_command',
+                              request,
+                              expect_errors = True ).json
+    assert_that( response, ErrorMatcher( ValueError,
+                            'Please specify a new name to rename it to.\n'
+                            'Usage: RefactorRename ' ) )
 
 
   @SharedYcmd
   def test_Subcommands_RefactorRename_Unicode( self, app ):
     unicode_test = PathToTestFile( 'testy', 'Unicode.cs' )
-    with WrapOmniSharpServer( app, unicode_test ):
-      contents = ReadFile( unicode_test )
-
-      request = BuildRequest( completer_target = 'filetype_default',
-                              command_arguments = [ 'RefactorRename', 'x' ],
-                              line_num = 30,
-                              column_num = 31,
-                              contents = contents,
-                              filetype = 'cs',
-                              filepath = unicode_test )
-      response = app.post_json( '/run_completer_command', request ).json
-      assert_that( response, has_entries( {
-        'fixits': contains_exactly( has_entries( {
-          'location': LocationMatcher( unicode_test, 30, 31 ),
-          'chunks': contains_exactly(
-            has_entries( {
-              'replacement_text': 'x',
-              'range': RangeMatcher( unicode_test, ( 30, 29 ), ( 30, 35 ) ) } )
-          ) } ) ) } ) )
+    contents = ReadFile( unicode_test )
+
+    request = BuildRequest( completer_target = 'filetype_default',
+                            command_arguments = [ 'RefactorRename', 'x' ],
+                            line_num = 30,
+                            column_num = 31,
+                            contents = contents,
+                            filetype = 'cs',
+                            filepath = unicode_test )
+    response = app.post_json( '/run_completer_command', request ).json
+    assert_that( response, has_entries( {
+      'fixits': contains_exactly( has_entries( {
+        'location': LocationMatcher( unicode_test, 30, 31 ),
+        'chunks': contains_exactly(
+          has_entries( {
+            'replacement_text': 'x',
+            'range': RangeMatcher( unicode_test, ( 30, 29 ), ( 30, 35 ) ) } )
+        ) } ) ) } ) )
 
 
   @SharedYcmd
   def test_Subcommands_RefactorRename_Basic( self, app ):
     continuous_test = PathToTestFile( 'testy', 'ContinuousTest.cs' )
-    with WrapOmniSharpServer( app, continuous_test ):
-      contents = ReadFile( continuous_test )
-
-      request = BuildRequest( completer_target = 'filetype_default',
-                              command_arguments = [ 'RefactorRename', 'x' ],
-                              line_num = 5,
-                              column_num = 15,
-                              contents = contents,
-                              filetype = 'cs',
-                              filepath = continuous_test )
-      response = app.post_json( '/run_completer_command', request ).json
-      assert_that( response, has_entries( {
-        'fixits': contains_exactly( has_entries( {
-          'location': LocationMatcher( continuous_test, 5, 15 ),
-          'chunks': contains_exactly(
-            has_entries( {
-              'replacement_text': 'x',
-              'range': RangeMatcher( continuous_test, ( 5, 15 ), ( 5, 29 ) ) } )
-          ) } ) ) } ) )
+    contents = ReadFile( continuous_test )
+
+    request = BuildRequest( completer_target = 'filetype_default',
+                            command_arguments = [ 'RefactorRename', 'x' ],
+                            line_num = 5,
+                            column_num = 15,
+                            contents = contents,
+                            filetype = 'cs',
+                            filepath = continuous_test )
+    response = app.post_json( '/run_completer_command', request ).json
+    assert_that( response, has_entries( {
+      'fixits': contains_exactly( has_entries( {
+        'location': LocationMatcher( continuous_test, 5, 15 ),
+        'chunks': contains_exactly(
+          has_entries( {
+            'replacement_text': 'x',
+            'range': RangeMatcher( continuous_test, ( 5, 15 ), ( 5, 29 ) ) } )
+        ) } ) ) } ) )
 
 
   @WithRetry()
@@ -306,399 +342,321 @@ def test_Subcommands_GoToSymbol( self, app ):
             PathToTestFile( 'testy', 'GotoTestCase.cs' ), 44, 15 ),
           LocationMatcher(
             PathToTestFile( 'testy', 'GotoTestCase.cs' ), 49, 15 ) ) ),
-      ( 'asd', ErrorMatcher( RuntimeError, 'No symbols found' ) )
+      ( 'asd', ErrorMatcher( RuntimeError, 'Symbol not found' ) )
     ]:
       with self.subTest( identifier = identifier, expected = expected ):
         filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-        with WrapOmniSharpServer( app, filepath ):
-          contents = ReadFile( filepath )
-          goto_data = BuildRequest( completer_target = 'filetype_default',
-                                    command_arguments = [ 'GoToSymbol',
-                                                          identifier ],
-                                    line_num = 1,
-                                    column_num = 1,
-                                    contents = contents,
-                                    filetype = 'cs',
-                                    filepath = filepath )
-          response =  app.post_json( '/run_completer_command',
-                                     goto_data,
-                                     expect_errors = True ).json
-          assert_that( response, expected )
+        contents = ReadFile( filepath )
+        goto_data = BuildRequest( completer_target = 'filetype_default',
+                                  command_arguments = [ 'GoToSymbol',
+                                                        identifier ],
+                                  line_num = 1,
+                                  column_num = 1,
+                                  contents = contents,
+                                  filetype = 'cs',
+                                  filepath = filepath )
+        response =  app.post_json( '/run_completer_command',
+                                   goto_data,
+                                   expect_errors = True ).json
+        assert_that( response, expected )
 
 
   @SharedYcmd
   def test_Subcommands_GoTo_Unicode( self, app ):
     filepath = PathToTestFile( 'testy', 'Unicode.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
+    contents = ReadFile( filepath )
 
-      goto_data = BuildRequest( completer_target = 'filetype_default',
-                                command_arguments = [ 'GoTo' ],
-                                line_num = 45,
-                                column_num = 43,
-                                contents = contents,
-                                filetype = 'cs',
-                                filepath = filepath )
+    goto_data = BuildRequest( completer_target = 'filetype_default',
+                              command_arguments = [ 'GoTo' ],
+                              line_num = 45,
+                              column_num = 43,
+                              contents = contents,
+                              filetype = 'cs',
+                              filepath = filepath )
 
-      response = app.post_json( '/run_completer_command', goto_data ).json
-      assert_that( response, LocationMatcher( filepath, 30, 54 ) )
+    response = app.post_json( '/run_completer_command', goto_data ).json
+    assert_that( response, LocationMatcher( filepath, 30, 54 ) )
 
 
   @SharedYcmd
   def test_Subcommands_GoToImplementation_Basic( self, app ):
     filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToImplementation' ],
-        line_num = 14,
-        column_num = 13,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
-
-      response = app.post_json( '/run_completer_command', goto_data ).json
-      assert_that( response, LocationMatcher( filepath, 31, 15 ) )
-
+    contents = ReadFile( filepath )
 
-  @SharedYcmd
-  def test_Subcommands_GoToImplementation_NoImplementation( self, app ):
-    filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToImplementation' ],
-        line_num = 18,
-        column_num = 13,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
+    goto_data = BuildRequest(
+      completer_target = 'filetype_default',
+      command_arguments = [ 'GoToImplementation' ],
+      line_num = 14,
+      column_num = 13,
+      contents = contents,
+      filetype = 'cs',
+      filepath = filepath
+    )
 
-      response =  app.post_json( '/run_completer_command',
-                                 goto_data,
-                                 expect_errors = True ).json
-      assert_that( response, ErrorMatcher( RuntimeError,
-                                           'No implementations found' ) )
+    response = app.post_json( '/run_completer_command', goto_data ).json
+    assert_that( response, LocationMatcher( filepath, 31, 15 ) )
 
 
   @SharedYcmd
-  def test_Subcommands_CsCompleter_InvalidLocation( self, app ):
+  def test_Subcommands_GoToImplementation_NoImplementation( self, app ):
     filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToImplementation' ],
-        line_num = 3,
-        column_num = 1,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
-
-      response =  app.post_json( '/run_completer_command',
-                                 goto_data,
-                                 expect_errors = True ).json
-      assert_that( response, ErrorMatcher( RuntimeError,
-                                           "Can't jump to implementation" ) )
-
+    contents = ReadFile( filepath )
 
-  @SharedYcmd
-  def test_Subcommands_GoToImplementationElseDeclaration_NoImplementation(
-      self, app ):
-    filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToImplementationElseDeclaration' ],
-        line_num = 18,
-        column_num = 13,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
+    goto_data = BuildRequest(
+      completer_target = 'filetype_default',
+      command_arguments = [ 'GoToImplementation' ],
+      line_num = 18,
+      column_num = 13,
+      contents = contents,
+      filetype = 'cs',
+      filepath = filepath
+    )
 
-      response = app.post_json( '/run_completer_command', goto_data ).json
-      assert_that( response, LocationMatcher( filepath, 36, 8 ) )
+    response =  app.post_json( '/run_completer_command',
+                               goto_data,
+                               expect_errors = True ).json
+    assert_that( response, ErrorMatcher( RuntimeError,
+                                         'Cannot jump to location' ) )
 
 
   @SharedYcmd
-  def test_Subcommands_GoToImplementationElseDeclaration_SingleImplementation(
-    self, app ):
+  def test_Subcommands_CsCompleter_InvalidLocation( self, app ):
     filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToImplementationElseDeclaration' ],
-        line_num = 14,
-        column_num = 13,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
-
-      response = app.post_json( '/run_completer_command', goto_data ).json
-      assert_that( response, LocationMatcher( filepath, 31, 15 ) )
-
+    contents = ReadFile( filepath )
 
-  @SharedYcmd
-  def test_Subcommands_GoToImplementationElseDeclaration_Multiple(
-    self, app ):
-    filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToImplementationElseDeclaration' ],
-        line_num = 22,
-        column_num = 13,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
+    goto_data = BuildRequest(
+      completer_target = 'filetype_default',
+      command_arguments = [ 'GoToImplementation' ],
+      line_num = 3,
+      column_num = 1,
+      contents = contents,
+      filetype = 'cs',
+      filepath = filepath
+    )
 
-      response = app.post_json( '/run_completer_command', goto_data ).json
-      assert_that( response,
-                   contains_exactly( LocationMatcher( filepath, 44, 15 ),
-                                     LocationMatcher( filepath, 49, 15 ) ) )
+    response =  app.post_json( '/run_completer_command',
+                               goto_data,
+                               expect_errors = True ).json
+    assert_that( response, ErrorMatcher( RuntimeError,
+                                         'Cannot jump to location' ) )
 
 
   @SharedYcmd
   def test_Subcommands_GoToReferences_InvalidLocation( self, app ):
     filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToReferences' ],
-        line_num = 3,
-        column_num = 1,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
+    contents = ReadFile( filepath )
 
-      response = app.post_json( '/run_completer_command',
-                                goto_data,
-                                expect_errors = True ).json
-      assert_that( response, ErrorMatcher(
-                               RuntimeError, 'No references found' ) )
+    goto_data = BuildRequest(
+      completer_target = 'filetype_default',
+      command_arguments = [ 'GoToReferences' ],
+      line_num = 3,
+      column_num = 1,
+      contents = contents,
+      filetype = 'cs',
+      filepath = filepath
+    )
+
+    response = app.post_json( '/run_completer_command',
+                              goto_data,
+                              expect_errors = True ).json
+    assert_that( response, ErrorMatcher(
+                             RuntimeError, 'Cannot jump to location' ) )
 
 
   @SharedYcmd
   def test_Subcommands_GoToReferences_MultipleReferences( self, app ):
     filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToReferences' ],
-        line_num = 18,
-        column_num = 4,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
+    contents = ReadFile( filepath )
 
-      response = app.post_json( '/run_completer_command', goto_data ).json
-      assert_that( response,
-                   contains_exactly( LocationMatcher( filepath, 17, 54 ),
-                                     LocationMatcher( filepath, 18, 4 ) ) )
+    goto_data = BuildRequest(
+      completer_target = 'filetype_default',
+      command_arguments = [ 'GoToReferences' ],
+      line_num = 18,
+      column_num = 4,
+      contents = contents,
+      filetype = 'cs',
+      filepath = filepath
+    )
+
+    response = app.post_json( '/run_completer_command', goto_data ).json
+    assert_that( response,
+                 contains_exactly( LocationMatcher( filepath, 17, 54 ),
+                                   LocationMatcher( filepath, 18, 4 ) ) )
 
 
   @SharedYcmd
   def test_Subcommands_GoToReferences_Basic( self, app ):
     filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToReferences' ],
-        line_num = 21,
-        column_num = 29,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
+    contents = ReadFile( filepath )
 
-      response = app.post_json( '/run_completer_command', goto_data ).json
-      assert_that( response, LocationMatcher( filepath, 21, 15 ) )
+    goto_data = BuildRequest(
+      completer_target = 'filetype_default',
+      command_arguments = [ 'GoToReferences' ],
+      line_num = 21,
+      column_num = 29,
+      contents = contents,
+      filetype = 'cs',
+      filepath = filepath
+    )
+
+    response = app.post_json( '/run_completer_command', goto_data ).json
+    assert_that( response, LocationMatcher( filepath, 21, 15 ) )
 
 
   @SharedYcmd
   def test_Subcommands_GetToImplementation_Unicode( self, app ):
     filepath = PathToTestFile( 'testy', 'Unicode.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      goto_data = BuildRequest(
-        completer_target = 'filetype_default',
-        command_arguments = [ 'GoToImplementation' ],
-        line_num = 48,
-        column_num = 44,
-        contents = contents,
-        filetype = 'cs',
-        filepath = filepath
-      )
+    contents = ReadFile( filepath )
 
-      response = app.post_json( '/run_completer_command', goto_data ).json
-      assert_that( response,
-                   contains_exactly( LocationMatcher( filepath, 49, 66 ),
-                                     LocationMatcher( filepath, 50, 62 ) ) )
+    goto_data = BuildRequest(
+      completer_target = 'filetype_default',
+      command_arguments = [ 'GoToImplementation' ],
+      line_num = 48,
+      column_num = 44,
+      contents = contents,
+      filetype = 'cs',
+      filepath = filepath
+    )
+
+    response = app.post_json( '/run_completer_command', goto_data ).json
+    assert_that( response,
+                 contains_exactly( LocationMatcher( filepath, 49, 66 ),
+                                   LocationMatcher( filepath, 50, 62 ) ) )
 
 
   @SharedYcmd
   def test_Subcommands_GetType_EmptyMessage( self, app ):
     filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
+    contents = ReadFile( filepath )
 
-      gettype_data = BuildRequest( completer_target = 'filetype_default',
-                                   command_arguments = [ 'GetType' ],
-                                   line_num = 1,
-                                   column_num = 1,
-                                   contents = contents,
-                                   filetype = 'cs',
-                                   filepath = filepath )
+    gettype_data = BuildRequest( completer_target = 'filetype_default',
+                                 command_arguments = [ 'GetType' ],
+                                 line_num = 1,
+                                 column_num = 1,
+                                 contents = contents,
+                                 filetype = 'cs',
+                                 filepath = filepath )
 
-      response = app.post_json( '/run_completer_command',
-                                gettype_data,
-                                expect_errors = True ).json
-      assert_that( response, ErrorMatcher( RuntimeError,
-                                           'No type info available.' ) )
+    response = app.post_json( '/run_completer_command',
+                              gettype_data,
+                              expect_errors = True ).json
+    assert_that( response, ErrorMatcher( RuntimeError,
+                                         'No type found.' ) )
 
 
   @SharedYcmd
   def test_Subcommands_GetType_VariableDeclaration( self, app ):
     filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
+    contents = ReadFile( filepath )
 
-      gettype_data = BuildRequest( completer_target = 'filetype_default',
-                                   command_arguments = [ 'GetType' ],
-                                   line_num = 5,
-                                   column_num = 5,
-                                   contents = contents,
-                                   filetype = 'cs',
-                                   filepath = filepath )
+    gettype_data = BuildRequest( completer_target = 'filetype_default',
+                                 command_arguments = [ 'GetType' ],
+                                 line_num = 5,
+                                 column_num = 9,
+                                 contents = contents,
+                                 filetype = 'cs',
+                                 filepath = filepath )
 
-      response = app.post_json( '/run_completer_command', gettype_data ).json
-      assert_that( response, has_entry( 'message', 'System.String' ) )
+    response = app.post_json( '/run_completer_command', gettype_data ).json
+    assert_that( response, has_entry( 'message',
+                                      '(local variable) string str' ) )
 
 
   @SharedYcmd
   def test_Subcommands_GetType_VariableUsage( self, app ):
     filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
+    contents = ReadFile( filepath )
 
-      gettype_data = BuildRequest( completer_target = 'filetype_default',
-                                   command_arguments = [ 'GetType' ],
-                                   line_num = 6,
-                                   column_num = 5,
-                                   contents = contents,
-                                   filetype = 'cs',
-                                   filepath = filepath )
+    gettype_data = BuildRequest( completer_target = 'filetype_default',
+                                 command_arguments = [ 'GetType' ],
+                                 line_num = 6,
+                                 column_num = 5,
+                                 contents = contents,
+                                 filetype = 'cs',
+                                 filepath = filepath )
 
-      response = app.post_json( '/run_completer_command', gettype_data ).json
-      assert_that( response, has_entry( 'message', 'string str' ) )
+    response = app.post_json( '/run_completer_command', gettype_data ).json
+    assert_that( response, has_entry( 'message',
+                                      '(local variable) string str' ) )
 
 
   @SharedYcmd
   def test_Subcommands_GetType_DocsIgnored( self, app ):
     filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
+    contents = ReadFile( filepath )
 
-      gettype_data = BuildRequest( completer_target = 'filetype_default',
-                                   command_arguments = [ 'GetType' ],
-                                   line_num = 10,
-                                   column_num = 34,
-                                   contents = contents,
-                                   filetype = 'cs',
-                                   filepath = filepath )
+    gettype_data = BuildRequest( completer_target = 'filetype_default',
+                                 command_arguments = [ 'GetType' ],
+                                 line_num = 10,
+                                 column_num = 34,
+                                 contents = contents,
+                                 filetype = 'cs',
+                                 filepath = filepath )
 
-      response = app.post_json( '/run_completer_command', gettype_data ).json
-      assert_that( response, has_entry(
-        'message', 'int GetTypeTestCase.an_int_with_docs' ) )
+    response = app.post_json( '/run_completer_command', gettype_data ).json
+    assert_that( response, has_entry(
+      'message', '(field) int GetTypeTestCase.an_int_with_docs' ) )
 
 
   @SharedYcmd
   def test_Subcommands_GetDoc_Invalid( self, app ):
     filepath = PathToTestFile( 'testy', 'GetDocTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
+    contents = ReadFile( filepath )
 
-      getdoc_data = BuildRequest( completer_target = 'filetype_default',
-                                  command_arguments = [ 'GetDoc' ],
-                                  line_num = 1,
-                                  column_num = 1,
-                                  contents = contents,
-                                  filetype = 'cs',
-                                  filepath = filepath )
+    getdoc_data = BuildRequest( completer_target = 'filetype_default',
+                                command_arguments = [ 'GetDoc' ],
+                                line_num = 1,
+                                column_num = 1,
+                                contents = contents,
+                                filetype = 'cs',
+                                filepath = filepath )
 
-      response = app.post_json( '/run_completer_command',
-                                getdoc_data,
-                                expect_errors = True ).json
-      assert_that( response, ErrorMatcher( RuntimeError,
-                                           'No documentation available.' ) )
+    response = app.post_json( '/run_completer_command',
+                              getdoc_data,
+                              expect_errors = True ).json
+    assert_that( response, ErrorMatcher( RuntimeError,
+                                         'No documentation available.' ) )
 
 
   @SharedYcmd
   def test_Subcommands_GetDoc_Variable( self, app ):
     filepath = PathToTestFile( 'testy', 'GetDocTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
+    contents = ReadFile( filepath )
 
-      getdoc_data = BuildRequest( completer_target = 'filetype_default',
-                                  command_arguments = [ 'GetDoc' ],
-                                  line_num = 13,
-                                  column_num = 28,
-                                  contents = contents,
-                                  filetype = 'cs',
-                                  filepath = filepath )
+    getdoc_data = BuildRequest( completer_target = 'filetype_default',
+                                command_arguments = [ 'GetDoc' ],
+                                line_num = 13,
+                                column_num = 28,
+                                contents = contents,
+                                filetype = 'cs',
+                                filepath = filepath )
 
-      response = app.post_json( '/run_completer_command', getdoc_data ).json
-      assert_that( response,
-                   has_entry( 'detailed_info',
-                              'int GetDocTestCase.an_int\n'
-                              'an integer, or something' ) )
+    response = app.post_json( '/run_completer_command', getdoc_data ).json
+    assert_that( response,
+                 has_entry( 'detailed_info',
+                            '(field) int GetDocTestCase.an_int\n\n'
+                            'an integer, or something' ) )
 
 
   @SharedYcmd
   def test_Subcommands_GetDoc_Function( self, app ):
     filepath = PathToTestFile( 'testy', 'GetDocTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
+    contents = ReadFile( filepath )
 
-      getdoc_data = BuildRequest( completer_target = 'filetype_default',
-                                  command_arguments = [ 'GetDoc' ],
-                                  line_num = 33,
-                                  column_num = 27,
-                                  contents = contents,
-                                  filetype = 'cs',
-                                  filepath = filepath )
+    getdoc_data = BuildRequest( completer_target = 'filetype_default',
+                                command_arguments = [ 'GetDoc' ],
+                                line_num = 37,
+                                column_num = 27,
+                                contents = contents,
+                                filetype = 'cs',
+                                filepath = filepath )
 
-      response = app.post_json( '/run_completer_command', getdoc_data ).json
-      assert_that( response, has_entry( 'detailed_info',
-        'int GetDocTestCase.DoATest()\n'
-        'Very important method.\n\nWith multiple lines of '
-        'commentary\nAnd Format-\n-ting' ) )
+    response = app.post_json( '/run_completer_command', getdoc_data ).json
+    # And here LSP OmniSharp-Roslyn messes up the formatting.
+    assert_that( response, has_entry( 'detailed_info',
+      'int GetDocTestCase.DoATest()\n\n'
+      'Very important method\\. With multiple lines of '
+      'commentary And Format\\- \\-ting' ) )
 
 
   @IsolatedYcmd()
@@ -737,28 +695,27 @@ def test_Subcommands_StopServer_DoNotKeepLogFiles( self, app ):
   @IsolatedYcmd()
   def test_Subcommands_RestartServer_PidChanges( self, app ):
     filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-
-      def GetPid():
-        request_data = BuildRequest( filetype = 'cs', filepath = filepath )
-        debug_info = app.post_json( '/debug_info', request_data ).json
-        return debug_info[ "completer" ][ "servers" ][ 0 ][ "pid" ]
-
-      old_pid = GetPid()
-
-      app.post_json(
-        '/run_completer_command',
-        BuildRequest(
-          filetype = 'cs',
-          filepath = filepath,
-          command_arguments = [ 'RestartServer' ]
-        )
+
+    def GetPid():
+      request_data = BuildRequest( filetype = 'cs', filepath = filepath )
+      debug_info = app.post_json( '/debug_info', request_data ).json
+      return debug_info[ "completer" ][ "servers" ][ 0 ][ "pid" ]
+
+    old_pid = GetPid()
+
+    app.post_json(
+      '/run_completer_command',
+      BuildRequest(
+        filetype = 'cs',
+        filepath = filepath,
+        command_arguments = [ 'RestartServer' ]
       )
-      WaitUntilCompleterServerReady( app, 'cs' )
+    )
+    WaitUntilCompleterServerReady( app, 'cs' )
 
-      new_pid = GetPid()
+    new_pid = GetPid()
 
-      assert old_pid != new_pid, '%r == %r' % ( old_pid, new_pid )
+    assert old_pid != new_pid, '%r == %r' % ( old_pid, new_pid )
 
 
   @IsolatedYcmd()
@@ -797,18 +754,53 @@ def test_Subcommands_StopServer_Timeout( self, app ):
   @SharedYcmd
   def test_Subcommands_Format_Works( self, app ):
     filepath = PathToTestFile( 'testy', 'Program.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
-
-      request = BuildRequest( command_arguments = [ 'Format' ],
-                              line_num = 1,
-                              column_num = 1,
-                              contents = contents,
-                              filetype = 'cs',
-                              filepath = filepath )
+    contents = ReadFile( filepath )
 
-      response = app.post_json( '/run_completer_command', request ).json
-      print( 'completer response = ', response )
+    request = BuildRequest( command_arguments = [ 'Format' ],
+                            line_num = 1,
+                            column_num = 1,
+                            contents = contents,
+                            filetype = 'cs',
+                            filepath = filepath )
+
+    request[ 'options' ] = {
+      'tab_size': 2,
+      'insert_spaces': True
+    }
+    response = app.post_json( '/run_completer_command', request ).json
+    print( 'completer response = ', response )
+    if OnWindows():
+      assert_that( response, has_entries( {
+        'fixits': contains_exactly( has_entries( {
+          'location': LocationMatcher( filepath, 1, 1 ),
+          'chunks': contains_exactly(
+            ChunkMatcher(
+              '\n        }\n    ',
+              LocationMatcher( filepath, 11, 1 ),
+              LocationMatcher( filepath, 12, 2 )
+            ),
+            ChunkMatcher(
+              '\n            ',
+              LocationMatcher( filepath, 9, 16 ),
+              LocationMatcher( filepath, 10, 4 )
+            ),
+            ChunkMatcher(
+              '\n        {\n            ',
+              LocationMatcher( filepath, 7, 42 ),
+              LocationMatcher( filepath, 9, 4 )
+            ),
+            ChunkMatcher(
+              '',
+              LocationMatcher( filepath, 7, 26 ),
+              LocationMatcher( filepath, 7, 27 )
+            ),
+            ChunkMatcher(
+              '\n    class MainClass\n    {\n        ',
+              LocationMatcher( filepath, 4, 2 ),
+              LocationMatcher( filepath, 7, 3 )
+            ),
+          ) } ) ) } ) )
+    else:
       assert_that( response, has_entries( {
         'fixits': contains_exactly( has_entries( {
           'location': LocationMatcher( filepath, 1, 1 ),
@@ -844,70 +836,65 @@ def test_Subcommands_Format_Works( self, app ):
   @SharedYcmd
   def test_Subcommands_RangeFormat_Works( self, app ):
     filepath = PathToTestFile( 'testy', 'Program.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      contents = ReadFile( filepath )
+    contents = ReadFile( filepath )
 
-      request = BuildRequest( command_arguments = [ 'Format' ],
-                              line_num = 11,
-                              column_num = 2,
-                              contents = contents,
-                              filetype = 'cs',
-                              filepath = filepath )
-      request[ 'range' ] = {
-        'start': { 'line_num':  8, 'column_num': 1 },
-        'end':   { 'line_num': 11, 'column_num': 4 }
-      }
-      response = app.post_json( '/run_completer_command', request ).json
-      print( 'completer response = ', response )
+    request = BuildRequest( command_arguments = [ 'Format' ],
+                            line_num = 11,
+                            column_num = 2,
+                            contents = contents,
+                            filetype = 'cs',
+                            filepath = filepath )
+    request[ 'range' ] = {
+      'start': { 'line_num':  8, 'column_num': 1 },
+      'end':   { 'line_num': 11, 'column_num': 4 }
+    }
+    request[ 'options' ] = {
+      'tab_size': 2,
+      'insert_spaces': True
+    }
+    response = app.post_json( '/run_completer_command', request ).json
+    print( 'completer response = ', response )
+    if OnWindows():
       assert_that( response, has_entries( {
         'fixits': contains_exactly( has_entries( {
           'location': LocationMatcher( filepath, 11, 2 ),
           'chunks': contains_exactly(
             ChunkMatcher(
-              '\n        ',
+              '\n        }\n    ',
               LocationMatcher( filepath, 11, 1 ),
-              LocationMatcher( filepath, 11, 3 )
+              LocationMatcher( filepath, 12, 2 )
             ),
             ChunkMatcher(
-              '            ',
-              LocationMatcher( filepath, 10, 1 ),
+              '\n            ',
+              LocationMatcher( filepath, 9, 16 ),
               LocationMatcher( filepath, 10, 4 )
             ),
             ChunkMatcher(
-              '        {\n            ',
-              LocationMatcher( filepath, 8, 1 ),
+              '\n        {\n            ',
+              LocationMatcher( filepath, 7, 42 ),
               LocationMatcher( filepath, 9, 4 )
             ),
           ) } ) ) } ) )
-
-
-  @SharedYcmd
-  def test_Subcommands_OrganizeImports( self, app ):
-    filepath = PathToTestFile( 'testy', 'ImportTest.cs' )
-    with WrapOmniSharpServer( app, filepath ):
-      request = BuildRequest( command_arguments = [ 'OrganizeImports' ],
-                              line_num = 11,
-                              column_num = 2,
-                              contents = ReadFile( filepath ),
-                              filetype = 'cs',
-                              filepath = filepath )
-
-      response = app.post_json( '/run_completer_command', request ).json
-      print( 'completer response = ', response )
+    else:
       assert_that( response, has_entries( {
         'fixits': contains_exactly( has_entries( {
           'location': LocationMatcher( filepath, 11, 2 ),
           'chunks': contains_exactly(
             ChunkMatcher(
-              '    ',
-              LocationMatcher( filepath, 5, 1 ),
-              LocationMatcher( filepath, 5, 2 ),
+              '\n        }\n    ',
+              LocationMatcher( filepath, 11, 1 ),
+              LocationMatcher( filepath, 12, 2 )
             ),
             ChunkMatcher(
-              '',
-              LocationMatcher( filepath, 1, 1 ),
-              LocationMatcher( filepath, 3, 1 ),
-            )
+              '            ',
+              LocationMatcher( filepath, 10, 1 ),
+              LocationMatcher( filepath, 10, 4 )
+            ),
+            ChunkMatcher(
+              '        {\n            ',
+              LocationMatcher( filepath, 8, 1 ),
+              LocationMatcher( filepath, 9, 4 )
+            ),
           ) } ) ) } ) )
 
 
@@ -916,61 +903,61 @@ def test_Subcommands_GoToDocumentOutline( self, app ):
 
     for filepath, expected in [
       ( PathToTestFile( 'testy', 'Empty.cs' ),
-        ErrorMatcher( RuntimeError, 'No symbols found' ) ),
+        ErrorMatcher( RuntimeError, 'Symbol not found' ) ),
       ( PathToTestFile( 'testy', 'SingleEntity.cs' ),
         LocationMatcher(
-          PathToTestFile( 'testy', 'SingleEntity.cs' ), 6, 8 ) ),
+          PathToTestFile( 'testy', 'SingleEntity.cs' ), 4, 1 ) ),
       ( PathToTestFile( 'testy', 'GotoTestCase.cs' ),
         has_items(
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 6, 8 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 4, 1 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 26, 12 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 6, 2 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 30, 8 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 30, 2 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 35, 12 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 43, 2 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 39, 12 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 48, 2 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 43, 8 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 27, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 48, 8 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 31, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 8, 15 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 36, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 13, 15 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 40, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 17, 15 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 44, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 21, 15 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 49, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 27, 8 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 13, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 31, 15 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 17, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 36, 8 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ),  8, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 40, 8 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 21, 3 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 44, 15 ),
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 26, 2 ),
+          LocationMatcher(
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 39, 2 ),
           LocationMatcher(
-            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 49, 15 ) ) )
+            PathToTestFile( 'testy', 'GotoTestCase.cs' ), 35, 2 ) ) )
     ]:
       with self.subTest( filepath = filepath, expected = expected ):
-        with WrapOmniSharpServer( app, filepath ):
-
-          # the command name and file are the only relevant arguments for this
-          # subcommand.  our current cursor position in the file doesn't matter.
-          request = BuildRequest( command_arguments = [ 'GoToDocumentOutline' ],
-                                  line_num = 0,
-                                  column_num = 0,
-                                  contents = ReadFile( filepath ),
-                                  filetype = 'cs',
-                                  filepath = filepath )
+        # the command name and file are the only relevant arguments for this
+        # subcommand.  our current cursor position in the file doesn't matter.
+        request = BuildRequest( command_arguments = [ 'GoToDocumentOutline' ],
+                                line_num = 0,
+                                column_num = 0,
+                                contents = ReadFile( filepath ),
+                                filetype = 'cs',
+                                filepath = filepath )
 
-          response = app.post_json( '/run_completer_command',
-                                    request,
-                                    expect_errors = True ).json
-          print( 'completer response = ', response )
-          assert_that( response, expected )
+        response = app.post_json( '/run_completer_command',
+                                  request,
+                                  expect_errors = True ).json
+        print( 'completer response = ', response )
+        assert_that( response, expected )
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/not-testy/Program.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/not-testy/Program.cs
deleted file mode 100644
index cac606c88f..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/not-testy/Program.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-
-namespace testy
-{
-	class MainClass
-	{
-		public static void Main (string[] args)
-		{
-			Console.
-		}
-	}
-}
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/not-testy/Properties/AssemblyInfo.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/not-testy/Properties/AssemblyInfo.cs
deleted file mode 100644
index 2121d05e99..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/not-testy/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-[assembly: AssemblyTitle ("testy")]
-[assembly: AssemblyDescription ("")]
-[assembly: AssemblyConfiguration ("")]
-[assembly: AssemblyCompany ("")]
-[assembly: AssemblyProduct ("")]
-[assembly: AssemblyCopyright ("valloric")]
-[assembly: AssemblyTrademark ("")]
-[assembly: AssemblyCulture ("")]
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion ("1.0.*")]
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/not-testy/testy.csproj b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/not-testy/testy.csproj
deleted file mode 100644
index e8b5f0cffc..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/not-testy/testy.csproj
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-  
-    Debug
-    x86
-    10.0.0
-    2.0
-    {0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}
-    Exe
-    testy
-    testy
-    v4.8
-    
-  
-  
-    true
-    full
-    false
-    bin\Debug
-    DEBUG;
-    prompt
-    4
-    true
-    x86
-  
-  
-    full
-    true
-    bin\Release
-    prompt
-    4
-    true
-    x86
-  
-  
-    
-  
-  
-    
-    
-  
-  
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/solution-named-like-folder.sln b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/solution-named-like-folder.sln
deleted file mode 100644
index 4fb02619aa..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/solution-named-like-folder.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy/testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = not-testy/testy.csproj
-	EndGlobalSection
-EndGlobal
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy.sln b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy.sln
deleted file mode 100644
index 82e3d1557a..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy/testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = testy/testy.csproj
-	EndGlobalSection
-EndGlobal
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy/Program.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy/Program.cs
deleted file mode 100644
index b4d0073db1..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy/Program.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-
-namespace testy
-{
-	class MainClass
-	{
-		public static void Main (string[] args)
-		{
-			int 九 = 9;
-			Console.
-		}
-	}
-}
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy/Properties/AssemblyInfo.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy/Properties/AssemblyInfo.cs
deleted file mode 100644
index 2121d05e99..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-[assembly: AssemblyTitle ("testy")]
-[assembly: AssemblyDescription ("")]
-[assembly: AssemblyConfiguration ("")]
-[assembly: AssemblyCompany ("")]
-[assembly: AssemblyProduct ("")]
-[assembly: AssemblyCopyright ("valloric")]
-[assembly: AssemblyTrademark ("")]
-[assembly: AssemblyCulture ("")]
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion ("1.0.*")]
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy/testy.csproj b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy/testy.csproj
deleted file mode 100644
index e8b5f0cffc..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy/testy.csproj
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-  
-    Debug
-    x86
-    10.0.0
-    2.0
-    {0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}
-    Exe
-    testy
-    testy
-    v4.8
-    
-  
-  
-    true
-    full
-    false
-    bin\Debug
-    DEBUG;
-    prompt
-    4
-    true
-    x86
-  
-  
-    full
-    true
-    bin\Release
-    prompt
-    4
-    true
-    x86
-  
-  
-    
-  
-  
-    
-    
-  
-  
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy2.sln b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy2.sln
deleted file mode 100644
index 82e3d1557a..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-named-like-folder/testy2.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy/testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = testy/testy.csproj
-	EndGlobalSection
-EndGlobal
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/.ycm_extra_conf.py b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/.ycm_extra_conf.py
deleted file mode 100644
index 54261e6531..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/.ycm_extra_conf.py
+++ /dev/null
@@ -1,9 +0,0 @@
-
-import os
-
-def CSharpSolutionFile( path, **kwargs ):
-  #remove '.ycm_extra_conf.py' and 'extra-conf-abs' from filename
-  location=os.path.dirname( __file__ )
-  location=os.path.dirname( location )
-  return os.path.join( location, 'testy2.sln' )
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy/Program.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy/Program.cs
deleted file mode 100644
index cac606c88f..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy/Program.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-
-namespace testy
-{
-	class MainClass
-	{
-		public static void Main (string[] args)
-		{
-			Console.
-		}
-	}
-}
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy/Properties/AssemblyInfo.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy/Properties/AssemblyInfo.cs
deleted file mode 100644
index 2121d05e99..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-[assembly: AssemblyTitle ("testy")]
-[assembly: AssemblyDescription ("")]
-[assembly: AssemblyConfiguration ("")]
-[assembly: AssemblyCompany ("")]
-[assembly: AssemblyProduct ("")]
-[assembly: AssemblyCopyright ("valloric")]
-[assembly: AssemblyTrademark ("")]
-[assembly: AssemblyCulture ("")]
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion ("1.0.*")]
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy/testy.csproj b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy/testy.csproj
deleted file mode 100644
index e8b5f0cffc..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy/testy.csproj
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-  
-    Debug
-    x86
-    10.0.0
-    2.0
-    {0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}
-    Exe
-    testy
-    testy
-    v4.8
-    
-  
-  
-    true
-    full
-    false
-    bin\Debug
-    DEBUG;
-    prompt
-    4
-    true
-    x86
-  
-  
-    full
-    true
-    bin\Release
-    prompt
-    4
-    true
-    x86
-  
-  
-    
-  
-  
-    
-    
-  
-  
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy2.sln b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy2.sln
deleted file mode 100644
index 82e3d1557a..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-abs/testy2.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy/testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = testy/testy.csproj
-	EndGlobalSection
-EndGlobal
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/.ycm_extra_conf.py b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/.ycm_extra_conf.py
deleted file mode 100644
index 22c2901ac7..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/.ycm_extra_conf.py
+++ /dev/null
@@ -1,4 +0,0 @@
-
-def CSharpSolutionFile( path, **kwargs ):
-  return "nosuch.sln"
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/Program.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/Program.cs
deleted file mode 100644
index cac606c88f..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/Program.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-
-namespace testy
-{
-	class MainClass
-	{
-		public static void Main (string[] args)
-		{
-			Console.
-		}
-	}
-}
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/Properties/AssemblyInfo.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/Properties/AssemblyInfo.cs
deleted file mode 100644
index 2121d05e99..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-[assembly: AssemblyTitle ("testy")]
-[assembly: AssemblyDescription ("")]
-[assembly: AssemblyConfiguration ("")]
-[assembly: AssemblyCompany ("")]
-[assembly: AssemblyProduct ("")]
-[assembly: AssemblyCopyright ("valloric")]
-[assembly: AssemblyTrademark ("")]
-[assembly: AssemblyCulture ("")]
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion ("1.0.*")]
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/testy.csproj b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/testy.csproj
deleted file mode 100644
index e8b5f0cffc..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy/testy.csproj
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-  
-    Debug
-    x86
-    10.0.0
-    2.0
-    {0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}
-    Exe
-    testy
-    testy
-    v4.8
-    
-  
-  
-    true
-    full
-    false
-    bin\Debug
-    DEBUG;
-    prompt
-    4
-    true
-    x86
-  
-  
-    full
-    true
-    bin\Release
-    prompt
-    4
-    true
-    x86
-  
-  
-    
-  
-  
-    
-    
-  
-  
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy2.sln b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy2.sln
deleted file mode 100644
index 82e3d1557a..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-bad/testy2.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy/testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = testy/testy.csproj
-	EndGlobalSection
-EndGlobal
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/.ycm_extra_conf.py b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/.ycm_extra_conf.py
deleted file mode 100644
index aaa3193ec5..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/.ycm_extra_conf.py
+++ /dev/null
@@ -1,4 +0,0 @@
-
-def CSharpSolutionFile( path, **kwargs ):
-  return "testy2.sln"
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/Program.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/Program.cs
deleted file mode 100644
index cac606c88f..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/Program.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-
-namespace testy
-{
-	class MainClass
-	{
-		public static void Main (string[] args)
-		{
-			Console.
-		}
-	}
-}
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/Properties/AssemblyInfo.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/Properties/AssemblyInfo.cs
deleted file mode 100644
index 2121d05e99..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-[assembly: AssemblyTitle ("testy")]
-[assembly: AssemblyDescription ("")]
-[assembly: AssemblyConfiguration ("")]
-[assembly: AssemblyCompany ("")]
-[assembly: AssemblyProduct ("")]
-[assembly: AssemblyCopyright ("valloric")]
-[assembly: AssemblyTrademark ("")]
-[assembly: AssemblyCulture ("")]
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion ("1.0.*")]
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/testy.csproj b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/testy.csproj
deleted file mode 100644
index e8b5f0cffc..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/testy.csproj
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-  
-    Debug
-    x86
-    10.0.0
-    2.0
-    {0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}
-    Exe
-    testy
-    testy
-    v4.8
-    
-  
-  
-    true
-    full
-    false
-    bin\Debug
-    DEBUG;
-    prompt
-    4
-    true
-    x86
-  
-  
-    full
-    true
-    bin\Release
-    prompt
-    4
-    true
-    x86
-  
-  
-    
-  
-  
-    
-    
-  
-  
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/testy2.sln b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/testy2.sln
deleted file mode 100644
index fff2c0f590..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy/testy2.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = testy/testy.csproj
-	EndGlobalSection
-EndGlobal
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy2.sln b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy2.sln
deleted file mode 100644
index 82e3d1557a..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/extra-conf-rel/testy2.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy/testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = testy/testy.csproj
-	EndGlobalSection
-EndGlobal
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy/Program.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy/Program.cs
deleted file mode 100644
index cac606c88f..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy/Program.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-
-namespace testy
-{
-	class MainClass
-	{
-		public static void Main (string[] args)
-		{
-			Console.
-		}
-	}
-}
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy/Properties/AssemblyInfo.cs b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy/Properties/AssemblyInfo.cs
deleted file mode 100644
index 2121d05e99..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-[assembly: AssemblyTitle ("testy")]
-[assembly: AssemblyDescription ("")]
-[assembly: AssemblyConfiguration ("")]
-[assembly: AssemblyCompany ("")]
-[assembly: AssemblyProduct ("")]
-[assembly: AssemblyCopyright ("valloric")]
-[assembly: AssemblyTrademark ("")]
-[assembly: AssemblyCulture ("")]
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion ("1.0.*")]
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy/testy.csproj b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy/testy.csproj
deleted file mode 100644
index e8b5f0cffc..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy/testy.csproj
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-  
-    Debug
-    x86
-    10.0.0
-    2.0
-    {0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}
-    Exe
-    testy
-    testy
-    v4.8
-    
-  
-  
-    true
-    full
-    false
-    bin\Debug
-    DEBUG;
-    prompt
-    4
-    true
-    x86
-  
-  
-    full
-    true
-    bin\Release
-    prompt
-    4
-    true
-    x86
-  
-  
-    
-  
-  
-    
-    
-  
-  
-
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy1.sln b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy1.sln
deleted file mode 100644
index 82e3d1557a..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy1.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy/testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = testy/testy.csproj
-	EndGlobalSection
-EndGlobal
diff --git a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy2.sln b/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy2.sln
deleted file mode 100644
index 82e3d1557a..0000000000
--- a/ycmd/tests/cs/testdata/testy-multiple-solutions/solution-not-named-like-folder/testy2.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testy", "testy/testy.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = testy/testy.csproj
-	EndGlobalSection
-EndGlobal
diff --git a/ycmd/tests/cs/testdata/testy/DiagnosticRange.cs b/ycmd/tests/cs/testdata/testy/DiagnosticRange.cs
deleted file mode 100644
index 2fc22e2bd5..0000000000
--- a/ycmd/tests/cs/testdata/testy/DiagnosticRange.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-public class Class
-{
-    public int attribute;
-    public static void Main (string[] args)
-    {
-        int 九 = 9;
-    }
-}
diff --git a/ycmd/tests/cs/testdata/testy/Empty.cs b/ycmd/tests/cs/testdata/testy/Empty.cs
index fbdadd554e..1372ec54f6 100644
--- a/ycmd/tests/cs/testdata/testy/Empty.cs
+++ b/ycmd/tests/cs/testdata/testy/Empty.cs
@@ -1,6 +1,2 @@
 using System;
 using System.Data;
-
-namespace testy
-{
-}
diff --git a/ycmd/tests/cs/testdata/testy/GetDocTestCase.cs b/ycmd/tests/cs/testdata/testy/GetDocTestCase.cs
index e54ac61ad9..76a1471fe7 100644
--- a/ycmd/tests/cs/testdata/testy/GetDocTestCase.cs
+++ b/ycmd/tests/cs/testdata/testy/GetDocTestCase.cs
@@ -1,33 +1,37 @@
-/**
- * testy is a namespace for testing
- */
+/// 
+/// testy is a namespace for testing
+/// 
 namespace testy {
-        /**
-         * Tests the GetDoc subcommands
-         */
+	/// 
+        /// Tests the GetDoc subcommands
+	/// 
 	public class GetDocTestCase {
-                /**
-                 * Constructor
-                 */
+		/// 
+                /// Constructor
+		/// 
 		public GetDocTestCase() {
                     this.an_int = 1;
 		}
 
-                /**
-                 * Very important method.
-                 *
-                 * With multiple lines of commentary
-                 *     And Format-
-                 * -ting
-                 */
+		/// 
+                /// Very important method.
+                ///
+                /// With multiple lines of commentary
+                ///     And Format-
+                /// -ting
+		/// 
                 public int DoATest() {
                     return an_int;
                 }
 
+		/// 
                 /// an integer, or something
+		/// 
                 private int an_int;
 
+		/// 
                 /// Use this for testing
+		/// 
                 private static void DoTesting() {
                     GetDocTestCase tc;
                     tc.DoATest();
diff --git a/ycmd/tests/cs/testdata/testy/MaxDiagnostics.cs b/ycmd/tests/cs/testdata/testy/MaxDiagnostics.cs
deleted file mode 100644
index ed5a2174c0..0000000000
--- a/ycmd/tests/cs/testdata/testy/MaxDiagnostics.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-public class MaxDiagnostics
-{
-    public int test;
-    public int test;
-    public int test;
-}
diff --git a/ycmd/tests/cs/testdata/testy/SingleEntity.cs b/ycmd/tests/cs/testdata/testy/SingleEntity.cs
index 594f1a8fea..fbdadd554e 100644
--- a/ycmd/tests/cs/testdata/testy/SingleEntity.cs
+++ b/ycmd/tests/cs/testdata/testy/SingleEntity.cs
@@ -3,7 +3,4 @@
 
 namespace testy
 {
-	class GotoTestCase
-	{
-	}
 }
diff --git a/ycmd/tests/cs/testdata/testy/ZeroColumnDiagnostic.cs b/ycmd/tests/cs/testdata/testy/ZeroColumnDiagnostic.cs
deleted file mode 100644
index 7e08027751..0000000000
--- a/ycmd/tests/cs/testdata/testy/ZeroColumnDiagnostic.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-class Class{
-    int Method()
-}
diff --git a/ycmd/tests/cs/testdata/testy/testy.csproj b/ycmd/tests/cs/testdata/testy/testy.csproj
index 6e2787b5ba..bac5eca164 100644
--- a/ycmd/tests/cs/testdata/testy/testy.csproj
+++ b/ycmd/tests/cs/testdata/testy/testy.csproj
@@ -1,55 +1,6 @@
-
-
+
   
-    Debug
-    x86
-    10.0.0
-    2.0
-    {0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}
     Exe
-    testy
-    testy
-    v4.8
-    
+    net8.0
   
-  
-    true
-    full
-    false
-    bin\Debug
-    DEBUG;
-    prompt
-    4
-    true
-    x86
-  
-  
-    full
-    true
-    bin\Release
-    prompt
-    4
-    true
-    x86
-  
-  
-    
-  
-  
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-  
-  
 
diff --git "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/Program.cs" "b/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/Program.cs"
deleted file mode 100644
index cac606c88f..0000000000
--- "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/Program.cs"	
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-
-namespace testy
-{
-	class MainClass
-	{
-		public static void Main (string[] args)
-		{
-			Console.
-		}
-	}
-}
diff --git "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/Properties/AssemblyInfo.cs" "b/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/Properties/AssemblyInfo.cs"
deleted file mode 100644
index 2121d05e99..0000000000
--- "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/Properties/AssemblyInfo.cs"	
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-[assembly: AssemblyTitle ("testy")]
-[assembly: AssemblyDescription ("")]
-[assembly: AssemblyConfiguration ("")]
-[assembly: AssemblyCompany ("")]
-[assembly: AssemblyProduct ("")]
-[assembly: AssemblyCopyright ("valloric")]
-[assembly: AssemblyTrademark ("")]
-[assembly: AssemblyCulture ("")]
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion ("1.0.*")]
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
-
diff --git "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/a project.csproj" "b/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/a project.csproj"
deleted file mode 100644
index 800f5a87a7..0000000000
--- "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/a project.csproj"	
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-  
-    Debug
-    x86
-    10.0.0
-    2.0
-    {0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}
-    Exe
-    testy
-    testy
-    v4.8
-    
-  
-  
-    true
-    full
-    false
-    bin\Debug
-    DEBUG;
-    prompt
-    4
-    true
-    x86
-  
-  
-    full
-    true
-    bin\Release
-    prompt
-    4
-    true
-    x86
-  
-  
-    
-  
-  
-    
-    
-  
-  
-
diff --git "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/a project.sln" "b/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/a project.sln"
deleted file mode 100644
index fc10298766..0000000000
--- "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/a project.sln"	
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "a project", "a project.csproj", "{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.ActiveCfg = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Debug|x86.Build.0 = Debug|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.ActiveCfg = Release|x86
-		{0C99F719-E00E-4CCD-AB9F-FEFBCD97C51F}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = a project.csproj
-	EndGlobalSection
-EndGlobal
diff --git "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/a project.userprefs" "b/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/a project.userprefs"
deleted file mode 100644
index 1895a66a4d..0000000000
--- "a/ycmd/tests/cs/testdata/\320\275\320\265\320\277\321\200\320\270\320\273\320\270\321\207\320\275\320\276\320\265 \321\201\320\273\320\276\320\262\320\276/a project.userprefs"	
+++ /dev/null
@@ -1,13 +0,0 @@
-
-  
-  
-    
-      
-      
-    
-  
-  
-    
-  
-  
-
\ No newline at end of file