from __future__ import print_function import sys, subprocess, json, os, select, shutil, time, socket termwidth = 150 print_communication = True def ordered(obj): if isinstance(obj, dict): return sorted((k, ordered(v)) for k, v in obj.items()) if isinstance(obj, list): return sorted(ordered(x) for x in obj) else: return obj def col_print(title, array): print() print() print(title) indentwidth = 4 indent = " " * indentwidth if not array: print(indent + "") return padwidth = 2 maxitemwidth = len(max(array, key=len)) numCols = max(1, int((termwidth - indentwidth + padwidth) / (maxitemwidth + padwidth))) numRows = len(array) // numCols + 1 pad = " " * padwidth for index in range(numRows): print(indent + pad.join(item.ljust(maxitemwidth) for item in array[index::numRows])) filterPacket = lambda x: x STDIN = 0 PIPE = 1 communicationMethods = [STDIN] if hasattr(socket, 'AF_UNIX'): communicationMethods.append(PIPE) def defaultExitWithError(proc): data = "" try: while select.select([proc.outPipe], [], [], 3.)[0]: data = data + proc.outPipe.read(1) if len(data): print("Rest of raw buffer from server:") printServer(data) except: pass proc.outPipe.close() proc.inPipe.close() proc.kill() sys.exit(1) exitWithError = lambda proc: defaultExitWithError(proc) serverTag = "SERVER" def printServer(*args): print(serverTag + ">", *args) print() sys.stdout.flush() def printClient(*args): print("CLIENT>", *args) print() sys.stdout.flush() def waitForRawMessage(cmakeCommand): stdoutdata = "" payload = "" while not cmakeCommand.poll(): stdoutdataLine = cmakeCommand.outPipe.readline() if stdoutdataLine: stdoutdata += stdoutdataLine.decode('utf-8') else: break begin = stdoutdata.find('[== "CMake Server" ==[\n') end = stdoutdata.find(']== "CMake Server" ==]') if begin != -1 and end != -1: begin += len('[== "CMake Server" ==[\n') payload = stdoutdata[begin:end] jsonPayload = json.loads(payload) filteredPayload = filterPacket(jsonPayload) if print_communication and filteredPayload: printServer(filteredPayload) if filteredPayload is not None or jsonPayload is None: return jsonPayload stdoutdata = stdoutdata[(end+len(']== "CMake Server" ==]')):] # Python2 has no problem writing the output of encodes directly, # but Python3 returns only 'int's for encode and so must be turned # into bytes. We use the existence of 'to_bytes' on an int to # determine which behavior is appropriate. It might be more clear # to do this in the code which uses the flag, but introducing # this lookup cost at every byte sent isn't ideal. has_to_bytes = "to_bytes" in dir(10) def writeRawData(cmakeCommand, content): writeRawData.counter += 1 payload = """ [== "CMake Server" ==[ %s ]== "CMake Server" ==] """ % content rn = ( writeRawData.counter % 2 ) == 0 if rn: payload = payload.replace('\n', '\r\n') if print_communication: printClient(content, "(Use \\r\\n:", rn, ")") # To stress test how cmake deals with fragmentation in the # communication channel, we send only one byte at a time. # Certain communication methods / platforms might still buffer # it all into one message since its so close together, but in # general this will catch places where we assume full buffers # come in all at once. encoded_payload = payload.encode('utf-8') # Python version 3+ can't write ints directly; but 'to_bytes' # for int was only added in python 3.2. If this is a 3+ version # of python without that conversion function; just write the whole # thing out at once. if sys.version_info[0] > 2 and not has_to_bytes: cmakeCommand.write(encoded_payload) else: for c in encoded_payload: if has_to_bytes: c = c.to_bytes(1, byteorder='big') cmakeCommand.write(c) writeRawData.counter = 0 def writePayload(cmakeCommand, obj): writeRawData(cmakeCommand, json.dumps(obj)) def getPipeName(): return "/tmp/server-test-socket" def attachPipe(cmakeCommand, pipeName): time.sleep(1) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(pipeName) global serverTag serverTag = "SERVER(PIPE)" cmakeCommand.outPipe = sock.makefile() cmakeCommand.inPipe = sock cmakeCommand.write = cmakeCommand.inPipe.sendall def writeAndFlush(pipe, val): pipe.write(val) pipe.flush() def initServerProc(cmakeCommand, comm): if comm == PIPE: pipeName = getPipeName() cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--pipe=" + pipeName]) attachPipe(cmakeCommand, pipeName) else: cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--debug"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) cmakeCommand.outPipe = cmakeCommand.stdout cmakeCommand.inPipe = cmakeCommand.stdin cmakeCommand.write = lambda val: writeAndFlush(cmakeCommand.inPipe, val) packet = waitForRawMessage(cmakeCommand) if packet == None: print("Not in server mode") sys.exit(2) if packet['type'] != 'hello': print("No hello message") sys.exit(3) return cmakeCommand def exitProc(cmakeCommand): # Tell the server to exit. cmakeCommand.stdin.close() cmakeCommand.stdout.close() # Wait for the server to exit. # If this version of python supports it, terminate the server after a timeout. try: cmakeCommand.wait(timeout=5) except TypeError: cmakeCommand.wait() except: cmakeCommand.terminate() raise def waitForMessage(cmakeCommand, expected): data = ordered(expected) packet = ordered(waitForRawMessage(cmakeCommand)) if packet != data: print ("Received unexpected message; test failed") exitWithError(cmakeCommand) return packet def waitForReply(cmakeCommand, originalType, cookie, skipProgress): gotResult = False while True: packet = waitForRawMessage(cmakeCommand) t = packet['type'] if packet['cookie'] != cookie or packet['inReplyTo'] != originalType: print("cookie or inReplyTo mismatch") sys.exit(4) if t == 'message' or t == 'progress': if skipProgress: continue if t == 'reply': break print("Unrecognized message", packet) sys.exit(5) return packet def waitForError(cmakeCommand, originalType, cookie, message): packet = waitForRawMessage(cmakeCommand) if packet['cookie'] != cookie or packet['type'] != 'error' or packet['inReplyTo'] != originalType or packet['errorMessage'] != message: sys.exit(6) def waitForProgress(cmakeCommand, originalType, cookie, current, message): packet = waitForRawMessage(cmakeCommand) if packet['cookie'] != cookie or packet['type'] != 'progress' or packet['inReplyTo'] != originalType or packet['progressCurrent'] != current or packet['progressMessage'] != message: sys.exit(7) def handshake(cmakeCommand, major, minor, source, build, generator, extraGenerator): version = { 'major': major } if minor >= 0: version['minor'] = minor writePayload(cmakeCommand, { 'type': 'handshake', 'protocolVersion': version, 'cookie': 'TEST_HANDSHAKE', 'sourceDirectory': source, 'buildDirectory': build, 'generator': generator, 'extraGenerator': extraGenerator }) waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE', False) def validateGlobalSettings(cmakeCommand, cmakeCommandPath, data): packet = waitForReply(cmakeCommand, 'globalSettings', '', False) capabilities = packet['capabilities'] # validate version: cmakeoutput = subprocess.check_output([ cmakeCommandPath, "--version" ], universal_newlines=True) cmakeVersion = cmakeoutput.splitlines()[0][14:] version = capabilities['version'] versionString = version['string'] vs = str(version['major']) + '.' + str(version['minor']) + '.' + str(version['patch']) if (versionString != vs and not versionString.startswith(vs + '-')): sys.exit(8) if (versionString != cmakeVersion): sys.exit(9) # validate generators: generatorObjects = capabilities['generators'] cmakeoutput = subprocess.check_output([ cmakeCommandPath, "--help" ], universal_newlines=True) index = cmakeoutput.index('\nGenerators\n\n') cmakeGenerators = [] for line in cmakeoutput[index + 12:].splitlines(): if not line: continue if line[0] == '*': # default generator marker line = ' ' + line[1:] if not line.startswith(' '): continue if line.startswith(' '): continue equalPos = line.find('=') tmp = '' if (equalPos > 0): tmp = line[2:equalPos].strip() else: tmp = line.strip() if tmp.endswith(" [arch]"): tmp = tmp[0:len(tmp) - 7] if (len(tmp) > 0) and (" - " not in tmp): cmakeGenerators.append(tmp) generators = [] for genObj in generatorObjects: generators.append(genObj['name']) generators.sort() cmakeGenerators.sort() for gen in cmakeGenerators: if (not gen in generators): sys.exit(10) gen = packet['generator'] if (gen != '' and not (gen in generators)): sys.exit(11) for i in data: print("Validating", i) if (packet[i] != data[i]): sys.exit(12) def validateCache(cmakeCommand, data): packet = waitForReply(cmakeCommand, 'cache', '', False) cache = packet['cache'] if (data['isEmpty']): if (cache != []): print('Expected empty cache, but got data.\n') sys.exit(1) return; if (cache == []): print('Expected cache contents, but got none.\n') sys.exit(1) hadHomeDir = False for value in cache: if (value['key'] == 'CMAKE_HOME_DIRECTORY'): hadHomeDir = True if (not hadHomeDir): print('No CMAKE_HOME_DIRECTORY found in cache.') sys.exit(1) def handleBasicMessage(proc, obj, debug): if 'sendRaw' in obj: data = obj['sendRaw'] if debug: print("Sending raw:", data) writeRawData(proc, data) return True elif 'send' in obj: data = obj['send'] if debug: print("Sending:", json.dumps(data)) writePayload(proc, data) return True elif 'recv' in obj: data = obj['recv'] if debug: print("Waiting for:", json.dumps(data)) waitForMessage(proc, data) return True elif 'message' in obj: print("MESSAGE:", obj["message"]) sys.stdout.flush() return True return False def shutdownProc(proc): # Tell the server to exit. proc.inPipe.close() proc.outPipe.close() # Wait for the server to exit. # If this version of python supports it, terminate the server after a timeout. try: proc.wait(timeout=5) except TypeError: proc.wait() except: proc.terminate() raise print('cmake-server exited: %d' % proc.returncode) sys.exit(proc.returncode)