diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 97770ed47..8cf1faadf 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -2722,6 +2722,15 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release ADD_TEST_MACRO(CMakeCommands.target_compile_definitions target_compile_definitions) ADD_TEST_MACRO(CMakeCommands.target_compile_options target_compile_options) + if(CMake_HAVE_SERVER_MODE) + # The cmake server-mode test requires python for a simple client. + find_package(PythonInterp QUIET) + if(PYTHON_EXECUTABLE) + set(Server_BUILD_OPTIONS -DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE}) + ADD_TEST_MACRO(Server Server) + endif() + endif() + configure_file( "${CMake_SOURCE_DIR}/Tests/CTestTestCrash/test.cmake.in" "${CMake_BINARY_DIR}/Tests/CTestTestCrash/test.cmake" diff --git a/Tests/Server/CMakeLists.txt b/Tests/Server/CMakeLists.txt new file mode 100644 index 000000000..8daf12a5e --- /dev/null +++ b/Tests/Server/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.4) +project(Server CXX) + +find_package(PythonInterp REQUIRED) + +macro(do_test bsname file) + execute_process(COMMAND ${PYTHON_EXECUTABLE} + "${CMAKE_SOURCE_DIR}/server-test.py" + "${CMAKE_COMMAND}" + "${CMAKE_SOURCE_DIR}/${file}" + "${CMAKE_SOURCE_DIR}" + "${CMAKE_BINARY_DIR}" + RESULT_VARIABLE test_result + ) + + if (NOT test_result EQUAL 0) + message(SEND_ERROR "TEST FAILED") + endif() +endmacro() + +do_test("test_handshake" "tc_handshake.json") + +add_executable(Server empty.cpp) diff --git a/Tests/Server/cmakelib.py b/Tests/Server/cmakelib.py new file mode 100644 index 000000000..48ebc89ae --- /dev/null +++ b/Tests/Server/cmakelib.py @@ -0,0 +1,126 @@ +import sys, subprocess, json + +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])) + +def waitForRawMessage(cmakeCommand): + stdoutdata = "" + payload = "" + while not cmakeCommand.poll(): + stdoutdataLine = cmakeCommand.stdout.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] + if print_communication: + print("\nSERVER>", json.loads(payload), "\n") + return json.loads(payload) + +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: + print("\nCLIENT>", content, "(Use \\r\\n:", rn, ")\n") + cmakeCommand.stdin.write(payload.encode('utf-8')) + cmakeCommand.stdin.flush() +writeRawData.counter = 0 + +def writePayload(cmakeCommand, obj): + writeRawData(cmakeCommand, json.dumps(obj)) + +def initProc(cmakeCommand): + cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + + packet = waitForRawMessage(cmakeCommand) + if packet == None: + print("Not in server mode") + sys.exit(1) + + if packet['type'] != 'hello': + print("No hello message") + sys.exit(1) + + return cmakeCommand + +def waitForMessage(cmakeCommand, expected): + data = ordered(expected) + packet = ordered(waitForRawMessage(cmakeCommand)) + + if packet != data: + sys.exit(-1) + return packet + +def waitForReply(cmakeCommand, originalType, cookie): + packet = waitForRawMessage(cmakeCommand) + if packet['cookie'] != cookie or packet['type'] != 'reply' or packet['inReplyTo'] != originalType: + sys.exit(1) + +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(1) + +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(1) + +def handshake(cmakeCommand, major, minor): + version = { 'major': major } + if minor >= 0: + version['minor'] = minor + + writePayload(cmakeCommand, { 'type': 'handshake', 'protocolVersion': version, 'cookie': 'TEST_HANDSHAKE' }) + waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE') diff --git a/Tests/Server/empty.cpp b/Tests/Server/empty.cpp new file mode 100644 index 000000000..766b7751b --- /dev/null +++ b/Tests/Server/empty.cpp @@ -0,0 +1,5 @@ + +int main() +{ + return 0; +} diff --git a/Tests/Server/server-test.py b/Tests/Server/server-test.py new file mode 100644 index 000000000..e0a3b3b6e --- /dev/null +++ b/Tests/Server/server-test.py @@ -0,0 +1,82 @@ +import sys, cmakelib, json + +debug = True + +cmakeCommand = sys.argv[1] +testFile = sys.argv[2] +sourceDir = sys.argv[3] +buildDir = sys.argv[4] + +print("SourceDir: ", sourceDir, " -- BuildDir: ", buildDir) + +proc = cmakelib.initProc(cmakeCommand) + +with open(testFile) as f: + testText = f.read() + testText = testText.replace('%BUILDDIR%', buildDir) + testText = testText.replace('%SOURCEDIR%', sourceDir) + testData = json.loads(testText) + +buildDir = sys.argv[3] +sourceDir = sys.argv[4] + +for obj in testData: + if 'sendRaw' in obj: + data = obj['sendRaw'] + if debug: print("Sending raw:", data) + cmakelib.writeRawData(proc, data) + elif 'send' in obj: + data = obj['send'] + if debug: print("Sending:", json.dumps(data)) + cmakelib.writePayload(proc, data) + elif 'recv' in obj: + data = obj['recv'] + if debug: print("Waiting for:", json.dumps(data)) + cmakelib.waitForMessage(proc, data) + elif 'reply' in obj: + data = obj['reply'] + if debug: print("Waiting for reply:", json.dumps(data)) + originalType = "" + cookie = "" + if 'cookie' in data: cookie = data['cookie'] + if 'type' in data: originalType = data['type'] + cmakelib.waitForReply(proc, originalType, cookie) + elif 'error' in obj: + data = obj['error'] + if debug: print("Waiting for error:", json.dumps(data)) + originalType = "" + cookie = "" + message = "" + if 'cookie' in data: cookie = data['cookie'] + if 'type' in data: originalType = data['type'] + if 'message' in data: message = data['message'] + cmakelib.waitForError(proc, originalType, cookie, message) + elif 'progress' in obj: + data = obj['progress'] + if debug: print("Waiting for progress:", json.dumps(data)) + originalType = '' + cookie = "" + current = 0 + message = "" + if 'cookie' in data: cookie = data['cookie'] + if 'type' in data: originalType = data['type'] + if 'current' in data: current = data['current'] + if 'message' in data: message = data['message'] + cmakelib.waitForProgress(proc, originalType, cookie, current, message) + elif 'handshake' in obj: + data = obj['handshake'] + if debug: print("Doing handshake:", json.dumps(data)) + major = -1 + minor = -1 + if 'major' in data: major = data['major'] + if 'minor' in data: minor = data['minor'] + cmakelib.handshake(proc, major, minor) + elif 'message' in obj: + print("MESSAGE:", obj["message"]) + else: + print("Unknown command:", json.dumps(obj)) + sys.exit(2) + + print("Completed") + +sys.exit(0) diff --git a/Tests/Server/tc_handshake.json b/Tests/Server/tc_handshake.json new file mode 100644 index 000000000..5261581c5 --- /dev/null +++ b/Tests/Server/tc_handshake.json @@ -0,0 +1,71 @@ +[ +{ "message": "Testing basic message handling:" }, + +{ "sendRaw": "Sometext"}, +{ "recv": {"cookie":"","errorMessage":"Failed to parse JSON input.","inReplyTo":"","type":"error"} }, + +{ "message": "Testing invalid json input"}, +{ "send": { "test": "sometext" } }, +{ "recv": {"cookie":"","errorMessage":"No type given in request.","inReplyTo":"","type":"error"} }, + +{ "send": {"test": "sometext","cookie":"monster"} }, +{ "recv": {"cookie":"monster","errorMessage":"No type given in request.","inReplyTo":"","type":"error"} }, + +{ "message": "Testing handshake" }, +{ "send": {"type": "sometype","cookie":"monster2"} }, +{ "recv": {"cookie":"monster2","errorMessage":"Waiting for type \"handshake\".","inReplyTo":"sometype","type":"error"} }, + +{ "send": {"type": "handshake"} }, +{ "recv": {"cookie":"","errorMessage":"\"protocolVersion\" is required for \"handshake\".","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"type": "handshake","foo":"bar"} }, +{ "recv": {"cookie":"","errorMessage":"\"protocolVersion\" is required for \"handshake\".","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"type": "handshake","protocolVersion":"bar"} }, +{ "recv": {"cookie":"","errorMessage":"\"protocolVersion\" must be a JSON object.","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"type": "handshake","protocolVersion":{}} }, +{ "recv": {"cookie":"","errorMessage":"\"major\" must be set and an integer.","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"type": "handshake","protocolVersion":{"major":"foo"}} }, +{ "recv": {"cookie":"","errorMessage":"\"major\" must be set and an integer.","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"type": "handshake","protocolVersion":{"major":1, "minor":"foo"}} }, +{ "recv": {"cookie":"","errorMessage":"\"minor\" must be unset or an integer.","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"type": "handshake","protocolVersion":{"major":-1, "minor":-1}} }, +{ "recv": {"cookie":"","errorMessage":"\"major\" must be >= 0.","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"type": "handshake","protocolVersion":{"major":10, "minor":-1}} }, +{ "recv": {"cookie":"","errorMessage":"\"minor\" must be >= 0 when set.","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"type": "handshake","protocolVersion":{"major":10000}} }, +{ "recv": {"cookie":"","errorMessage":"Protocol version not supported.","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"type": "handshake","protocolVersion":{"major":1, "minor":10000}} }, +{ "recv": {"cookie":"","errorMessage":"Protocol version not supported.","inReplyTo":"handshake","type":"error"} }, + +{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1}} }, +{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"buildDirectory\" is missing."} }, + +{ "message": "Testing protocol version specific options (1.0):" }, +{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":"/tmp/src"} }, +{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"buildDirectory\" is missing."} }, + +{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":"/tmp/src","buildDirectory":"/tmp/build"} }, +{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"sourceDirectory\" is not a directory."} }, + +{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","extraGenerator":"CodeBlocks"} }, +{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"generator\" is unset but required."} }, + +{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","generator":"XXXX","extraGenerator":"CodeBlocks"} }, +{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: Could not set up the requested combination of \"generator\" and \"extraGenerator\""} }, + +{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","generator":"Ninja","extraGenerator":"XXXX"} }, +{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: Could not set up the requested combination of \"generator\" and \"extraGenerator\""} }, + +{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","generator":"Ninja","extraGenerator":"CodeBlocks"} }, +{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"reply"} }, + +{ "message": "Everything ok." } +]