summaryrefslogtreecommitdiffstats
path: root/Tests/Server
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/Server')
-rw-r--r--Tests/Server/CMakeLists.txt13
-rw-r--r--Tests/Server/cmakelib.py225
-rw-r--r--Tests/Server/server-test.py177
-rw-r--r--Tests/Server/tc_cache.json24
-rw-r--r--Tests/Server/tc_handshake.json4
5 files changed, 317 insertions, 126 deletions
diff --git a/Tests/Server/CMakeLists.txt b/Tests/Server/CMakeLists.txt
index e7eaa8d..41d1131 100644
--- a/Tests/Server/CMakeLists.txt
+++ b/Tests/Server/CMakeLists.txt
@@ -3,10 +3,10 @@ project(Server CXX)
find_package(PythonInterp REQUIRED)
-macro(do_test bsname file)
+macro(do_test bsname file type)
execute_process(COMMAND ${PYTHON_EXECUTABLE}
-B # no .pyc files
- "${CMAKE_SOURCE_DIR}/server-test.py"
+ "${CMAKE_SOURCE_DIR}/${type}-test.py"
"${CMAKE_COMMAND}"
"${CMAKE_SOURCE_DIR}/${file}"
"${CMAKE_SOURCE_DIR}"
@@ -16,12 +16,13 @@ macro(do_test bsname file)
)
if (NOT test_result EQUAL 0)
- message(SEND_ERROR "TEST FAILED")
+ message(SEND_ERROR "TEST FAILED: ${test_result}")
endif()
endmacro()
-do_test("test_handshake" "tc_handshake.json")
-do_test("test_globalSettings" "tc_globalSettings.json")
-do_test("test_buildsystem1" "tc_buildsystem1.json")
+do_test("test_cache" "tc_cache.json" "server")
+do_test("test_handshake" "tc_handshake.json" "server")
+do_test("test_globalSettings" "tc_globalSettings.json" "server")
+do_test("test_buildsystem1" "tc_buildsystem1.json" "server")
add_executable(Server empty.cpp)
diff --git a/Tests/Server/cmakelib.py b/Tests/Server/cmakelib.py
index 78450d5..6e8761a 100644
--- a/Tests/Server/cmakelib.py
+++ b/Tests/Server/cmakelib.py
@@ -1,5 +1,5 @@
from __future__ import print_function
-import sys, subprocess, json
+import sys, subprocess, json, os, select, shutil, time, socket
termwidth = 150
@@ -38,11 +38,50 @@ def col_print(title, array):
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.stdout.readline()
+ stdoutdataLine = cmakeCommand.outPipe.readline()
if stdoutdataLine:
stdoutdata += stdoutdataLine.decode('utf-8')
else:
@@ -50,12 +89,24 @@ def waitForRawMessage(cmakeCommand):
begin = stdoutdata.find('[== "CMake Server" ==[\n')
end = stdoutdata.find(']== "CMake Server" ==]')
- if (begin != -1 and end != -1):
+ 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)
+ 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
@@ -71,36 +122,96 @@ def writeRawData(cmakeCommand, content):
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()
+ 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 initProc(cmakeCommand):
- cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--debug"],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE)
+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(1)
+ sys.exit(2)
if packet['type'] != 'hello':
print("No hello message")
- sys.exit(1)
+ 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:
- sys.exit(-1)
+ print ("Received unexpected message; test failed")
+ exitWithError(cmakeCommand)
return packet
def waitForReply(cmakeCommand, originalType, cookie, skipProgress):
@@ -109,25 +220,27 @@ def waitForReply(cmakeCommand, originalType, cookie, skipProgress):
packet = waitForRawMessage(cmakeCommand)
t = packet['type']
if packet['cookie'] != cookie or packet['inReplyTo'] != originalType:
- sys.exit(1)
+ print("cookie or inReplyTo mismatch")
+ sys.exit(4)
if t == 'message' or t == 'progress':
if skipProgress:
continue
if t == 'reply':
break
- sys.exit(1)
+ 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(1)
+ 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(1)
+ sys.exit(7)
def handshake(cmakeCommand, major, minor, source, build, generator, extraGenerator):
version = { 'major': major }
@@ -152,9 +265,9 @@ def validateGlobalSettings(cmakeCommand, cmakeCommandPath, data):
versionString = version['string']
vs = str(version['major']) + '.' + str(version['minor']) + '.' + str(version['patch'])
if (versionString != vs and not versionString.startswith(vs + '-')):
- sys.exit(1)
+ sys.exit(8)
if (versionString != cmakeVersion):
- sys.exit(1)
+ sys.exit(9)
# validate generators:
generatorObjects = capabilities['generators']
@@ -187,13 +300,77 @@ def validateGlobalSettings(cmakeCommand, cmakeCommandPath, data):
for gen in cmakeGenerators:
if (not gen in generators):
- sys.exit(1)
+ sys.exit(10)
gen = packet['generator']
if (gen != '' and not (gen in generators)):
- sys.exit(1)
+ 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)
diff --git a/Tests/Server/server-test.py b/Tests/Server/server-test.py
index 62d9008..701c6e9 100644
--- a/Tests/Server/server-test.py
+++ b/Tests/Server/server-test.py
@@ -9,7 +9,7 @@ sourceDir = sys.argv[3]
buildDir = sys.argv[4] + "/" + os.path.splitext(os.path.basename(testFile))[0]
cmakeGenerator = sys.argv[5]
-print("Test:", testFile,
+print("Server Test:", testFile,
"\n-- SourceDir:", sourceDir,
"\n-- BuildDir:", buildDir,
"\n-- Generator:", cmakeGenerator)
@@ -17,104 +17,89 @@ print("Test:", testFile,
if os.path.exists(buildDir):
shutil.rmtree(buildDir)
-proc = cmakelib.initProc(cmakeCommand)
+cmakelib.filterBase = sourceDir
with open(testFile) as f:
testData = json.loads(f.read())
-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 = ""
- skipProgress = False;
- if 'cookie' in data: cookie = data['cookie']
- if 'type' in data: originalType = data['type']
- if 'skipProgress' in data: skipProgress = data['skipProgress']
- cmakelib.waitForReply(proc, originalType, cookie, skipProgress)
- 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
- generator = cmakeGenerator
- extraGenerator = ''
- sourceDirectory = sourceDir
- buildDirectory = buildDir
- if 'major' in data: major = data['major']
- if 'minor' in data: minor = data['minor']
- if 'buildDirectory' in data: buildDirectory = data['buildDirectory']
- if 'sourceDirectory' in data: sourceDirectory = data['sourceDirectory']
- if 'generator' in data: generator = data['generator']
- if 'extraGenerator' in data: extraGenerator = data['extraGenerator']
- if not os.path.isabs(buildDirectory):
- buildDirectory = buildDir + "/" + buildDirectory
- if not os.path.isabs(sourceDirectory):
- sourceDirectory = sourceDir + "/" + sourceDirectory
- cmakelib.handshake(proc, major, minor, sourceDirectory, buildDirectory,
- generator, extraGenerator)
- elif 'validateGlobalSettings' in obj:
- data = obj['validateGlobalSettings']
- if not 'buildDirectory' in data: data['buildDirectory'] = buildDir
- if not 'sourceDirectory' in data: data['sourceDirectory'] = sourceDir
- if not 'generator' in data: data['generator'] = cmakeGenerator
- if not 'extraGenerator' in data: data['extraGenerator'] = ''
- cmakelib.validateGlobalSettings(proc, cmakeCommand, data)
- elif 'message' in obj:
- print("MESSAGE:", obj["message"])
- else:
- print("Unknown command:", json.dumps(obj))
- sys.exit(2)
+for communicationMethod in cmakelib.communicationMethods:
+ proc = cmakelib.initServerProc(cmakeCommand, communicationMethod)
+ if proc is None:
+ continue
- print("Completed")
-
-# Tell the server to exit.
-proc.stdin.close()
-proc.stdout.close()
+ for obj in testData:
+ if cmakelib.handleBasicMessage(proc, obj, debug):
+ pass
+ elif 'reply' in obj:
+ data = obj['reply']
+ if debug: print("Waiting for reply:", json.dumps(data))
+ originalType = ""
+ cookie = ""
+ skipProgress = False;
+ if 'cookie' in data: cookie = data['cookie']
+ if 'type' in data: originalType = data['type']
+ if 'skipProgress' in data: skipProgress = data['skipProgress']
+ cmakelib.waitForReply(proc, originalType, cookie, skipProgress)
+ 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
+ generator = cmakeGenerator
+ extraGenerator = ''
+ sourceDirectory = sourceDir
+ buildDirectory = buildDir
+ if 'major' in data: major = data['major']
+ if 'minor' in data: minor = data['minor']
+ if 'buildDirectory' in data: buildDirectory = data['buildDirectory']
+ if 'sourceDirectory' in data: sourceDirectory = data['sourceDirectory']
+ if 'generator' in data: generator = data['generator']
+ if 'extraGenerator' in data: extraGenerator = data['extraGenerator']
-# 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
-
-sys.exit(0)
+ if not os.path.isabs(buildDirectory):
+ buildDirectory = buildDir + "/" + buildDirectory
+ if sourceDirectory != '' and not os.path.isabs(sourceDirectory):
+ sourceDirectory = sourceDir + "/" + sourceDirectory
+ cmakelib.handshake(proc, major, minor, sourceDirectory, buildDirectory,
+ generator, extraGenerator)
+ elif 'validateGlobalSettings' in obj:
+ data = obj['validateGlobalSettings']
+ if not 'buildDirectory' in data: data['buildDirectory'] = buildDir
+ if not 'sourceDirectory' in data: data['sourceDirectory'] = sourceDir
+ if not 'generator' in data: data['generator'] = cmakeGenerator
+ if not 'extraGenerator' in data: data['extraGenerator'] = ''
+ cmakelib.validateGlobalSettings(proc, cmakeCommand, data)
+ elif 'validateCache' in obj:
+ data = obj['validateCache']
+ if not 'isEmpty' in data: data['isEmpty'] = false
+ cmakelib.validateCache(proc, data)
+ elif 'reconnect' in obj:
+ cmakelib.exitProc(proc)
+ proc = cmakelib.initServerProc(cmakeCommand, communicationMethod)
+ else:
+ print("Unknown command:", json.dumps(obj))
+ sys.exit(2)
+ cmakelib.shutdownProc(proc)
+ print("Completed")
diff --git a/Tests/Server/tc_cache.json b/Tests/Server/tc_cache.json
new file mode 100644
index 0000000..74af6d9
--- /dev/null
+++ b/Tests/Server/tc_cache.json
@@ -0,0 +1,24 @@
+[
+{ "message": "Testing cache" },
+
+{ "message": "Cache after first handshake is empty:" },
+{ "handshake": {"major": 1, "sourceDirectory": "buildsystem1", "buildDirectory": "buildsystem1"} },
+{ "send": { "type": "cache" } },
+{ "validateCache": { "isEmpty": true } },
+
+{ "message": "Cache after configure is populated:" },
+{ "send": { "type": "configure" } },
+{ "reply": { "type": "configure", "skipProgress":true } },
+{ "send": { "type": "cache" } },
+{ "validateCache": { "isEmpty": false } },
+
+{ "message": "Handshake for existing cache requires buildDirectory only:" },
+{ "reconnect": {} },
+{ "handshake": {"major": 1, "sourceDirectory": "", "buildDirectory": "buildsystem1"} },
+
+{ "message": "Cache after reconnect is again populated:" },
+{ "send": { "type": "cache" } },
+{ "validateCache": { "isEmpty": false } },
+
+{ "message": "Everything ok." }
+]
diff --git a/Tests/Server/tc_handshake.json b/Tests/Server/tc_handshake.json
index 975bb3d..4bb7fa7 100644
--- a/Tests/Server/tc_handshake.json
+++ b/Tests/Server/tc_handshake.json
@@ -11,6 +11,10 @@
{ "send": {"test": "sometext","cookie":"monster"} },
{ "recv": {"cookie":"monster","errorMessage":"No type given in request.","inReplyTo":"","type":"error"} },
+{ "message": "Testing commands before handshake" },
+{ "send": {"type": "cache","cookie":"monster"} },
+{ "recv": {"cookie":"monster","errorMessage":"Waiting for type \"handshake\".","inReplyTo":"cache","type":"error"} },
+
{ "message": "Testing handshake" },
{ "send": {"type": "sometype","cookie":"monster2"} },
{ "recv": {"cookie":"monster2","errorMessage":"Waiting for type \"handshake\".","inReplyTo":"sometype","type":"error"} },