diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a413d9ab273af7f8ccb13e2c3e406b77f03bc37a..da4c4a0355c5f42409f63c4e4f077e135da24fbb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,4 +12,4 @@ unit_tests: - tdaq script: - source /afs/cern.ch/atlas/project/tdaq/cmake/cmake_tdaq/bin/cm_setup.sh nightly - - python -m unittest discover --top-level-directory "Script/" --start-directory "Script/UnitTests/" --pattern "Test_*.py" --verbose \ No newline at end of file + - python -m unittest discover --top-level-directory "Script/" --start-directory "Script/UnitTests/" --pattern "*Test.py" --verbose \ No newline at end of file diff --git a/Script/TestSuite_Functional.py b/Script/TestSuite_Functional.py new file mode 100644 index 0000000000000000000000000000000000000000..d5f8523f71b2b0478ffda5cb301271eef0414c8d --- /dev/null +++ b/Script/TestSuite_Functional.py @@ -0,0 +1,14 @@ +import unittest +from UnitTests.FunctionalTests import LocalCopy_Test, ERS_Test + +suite = unittest.TestSuite() +loader = unittest.TestLoader() + +suite.addTests(loader.loadTestsFromModule(LocalCopy_Test)) +suite.addTests(loader.loadTestsFromModule(ERS_Test)) + +runner = unittest.TextTestRunner(verbosity=3) +print("\n==============================================================") +print("Running Functional Tests:") +print("==============================================================\n") +result = runner.run(suite) \ No newline at end of file diff --git a/Script/TestSuite_UnitTests.py b/Script/TestSuite_UnitTests.py new file mode 100644 index 0000000000000000000000000000000000000000..b8e2b5e50433dc4718ac15cfd2a37a3824f9d544 --- /dev/null +++ b/Script/TestSuite_UnitTests.py @@ -0,0 +1,13 @@ +import unittest +from UnitTests import BaseFileNameParser_Test + +suite = unittest.TestSuite() +loader = unittest.TestLoader() + +suite.addTests(loader.loadTestsFromModule(BaseFileNameParser_Test)) + +runner = unittest.TextTestRunner(verbosity=3) +print("\n==============================================================") +print("Running Unit Tests:") +print("==============================================================\n") +result = runner.run(suite) \ No newline at end of file diff --git a/Script/UnitTests/Test_BaseFileNameParser.py b/Script/UnitTests/BaseFileNameParser_Test.py similarity index 100% rename from Script/UnitTests/Test_BaseFileNameParser.py rename to Script/UnitTests/BaseFileNameParser_Test.py diff --git a/Script/UnitTests/FunctionalTests/ERS_Test.py b/Script/UnitTests/FunctionalTests/ERS_Test.py new file mode 100644 index 0000000000000000000000000000000000000000..869dd9e9f25e0b631a194385ff9094b048a72fe6 --- /dev/null +++ b/Script/UnitTests/FunctionalTests/ERS_Test.py @@ -0,0 +1,93 @@ +import unittest +import signal +import shutil +import time +from subprocess import Popen + +if __name__ == '__main__': + import sys + from os.path import dirname, abspath, join + # add CastorScript/Script to path so imports keeps working + SCRIPT_DIR = abspath(join(dirname(__file__), '../..')) + sys.path.append(SCRIPT_DIR) + +from UnitTests.FunctionalTests.FuncTestUtils.DirUtils import createTmpDir + +# I'm a developer, I want to see if the script is able to publish to ERS. +# However, I don't have access to a partition or an MTS stream. +# Because of this I want to redirect output to STDOUT in a file, to see that it works. +class TestERStoSTDOUT(unittest.TestCase): + + @classmethod + def setUpClass(self): + + # I create test directories and remember their locations. + ## returns a dictionary over dirs like src des log config etc.. + self.dirs = createTmpDir("/Script/UnitTests/FunctionalTests") + + # I make a config file from the template (CastorScript/Configs) + # I find the config file and I open it + cfgTemplate = open(self.dirs["config"] + "/template.cfg", "r") + + # I make a new file in CastorScript/tmp directory + cfgTest = open(self.dirs["tmp"] + "/funcTestConf.cfg", "w") + + custom_cfg_lines = { + "LogDir: ": "LogDir: \'{}\'\n".format(self.dirs["log"]), + "DirList: ": "DirList: [\'{}\',]\n".format(self.dirs["src"]), + "CopyDir: ": "CopyDir: \'{}\'\n".format(self.dirs["des"]), + "ers_info: ": "ers_info: \'\'\n", + "ers_warning: ": "ers_warning: \'\'\n", + "ers_error: ": "ers_error: \'\'\n", + "ers_debug: ": "ers_debug: \'\'\n", + "ERSenabled: ": "ERSenabled: True\n" + } + + # And I copy the content and replaces the directories with my own dirs + for line in cfgTemplate: + for key, value in custom_cfg_lines.iteritems(): + if line.find(key) is not -1: + line = value + cfgTest.write(line) + + cfgTest.close() + + + # Now I Run the script with config + self.output = open(self.dirs["tmp"] + "/" + "std.output", "a") + self.output.flush() + + ## TODO More cleanup + scriptcommand = ["python","-u", self.dirs["project"] + "/Script/CastorScript.py", self.dirs["tmp"] + "/funcTestConf.cfg" ] + self.castorscriptprocess = Popen(scriptcommand, stdout=self.output, stderr=self.output) + + + @classmethod + def tearDownClass(self): + self.output.close() + ## remove directories and files + shutil.rmtree(self.dirs["tmp"]) + + + ## Test cases: + def test_subStringFoundInStdout(self): + # I wait for the messages to propagate + time.sleep(2) + # I stop the process to collect output + self.castorscriptprocess.send_signal(signal.SIGUSR2) + self.castorscriptprocess.wait() + self.output.close() + + # I look at the file and observes that the substring is there. + self.output = open(self.dirs["tmp"] + "/" + "std.output", "r") + foundSubstring = False + for line in self.output: + if line.find("CastorScript is now logging to partition") > -1: + foundSubstring = True + + # Now I know that ERS works, and I feel a bit safer + self.assertEqual(foundSubstring, True) + + +if __name__ == '__main__': + unittest.main() diff --git a/Script/UnitTests/FunctionalTests/FuncTestUtils/DirUtils.py b/Script/UnitTests/FunctionalTests/FuncTestUtils/DirUtils.py new file mode 100644 index 0000000000000000000000000000000000000000..e81b7745341d2f46c951c8f746c41fb267a273d7 --- /dev/null +++ b/Script/UnitTests/FunctionalTests/FuncTestUtils/DirUtils.py @@ -0,0 +1,96 @@ +import time +from subprocess import Popen, PIPE +#For timing timeouts, timer starts with __init__ function +class Timer(): + def __init__(self, seconds): + self.startTime = time.time() + self.endTime = self.startTime + seconds + self.timedout = False + + def timesUp(self): + if (self.endTime < time.time()): + self.timedout = True + return True + else: + return False + + def timesNotUp(self): + return not self.timesUp() + +def createTmpDir(testLocation): + """Creates the directories: tmp, des, src and log at the defined testLocation. + Testlocation is the relative path from the CastorScript folder to where the folders should be created. + returns a dictionary of paths to relevant directories: projectPath, configPath, tmpPath, srcPath, desPath and logPath.""" + import os, shutil + + dirs = {} + + ## Define folder structure + dirs["project"] = os.path.dirname(os.path.abspath(__name__)) + + thisDir = dirs["project"] + testLocation + + dirs["tmp"] = thisDir + "/tmp" + dirs["config"] = dirs["project"] + "/Configs" + + dirs["src"] = dirs["tmp"] + "/src" + dirs["des"] = dirs["tmp"] + "/des" + dirs["log"] = dirs["tmp"] + "/log" + + # I create some folders + try: + os.makedirs(dirs["src"]) + os.makedirs(dirs["des"]) + os.makedirs(dirs["log"]) + except: + shutil.rmtree(dirs["tmp"]) + os.makedirs(dirs["src"]) + os.makedirs(dirs["des"]) + os.makedirs(dirs["log"]) + + return dirs + + + +class DirectoryChecker(): + """For checking the contents of a directory either once, or until a condition is met or timeout is reached.""" + def __init__(self, dirPath): + self.dirPath = dirPath + self.lsOutput = None + self.wasEmpty = None + self.timerUtil = None + + def waitForAnyFile(self, seconds): + "Waits for the specified amount of time for the dir to be empty, returns a tuple (bool: timedout, str: lsOutput)" + self.timerUtil = Timer(seconds) + while self.checkIfEmpty() and self.timerUtil.timesNotUp(): + pass + return (self.timerUtil.timedout, self.getLsOutput()) + + def waitForEmptyDir(self, seconds): + "Waits for the specified amount of time for the dir to contain files, returns a tuple (bool: timedout, str: lsOutput)" + self.timerUtil = Timer(seconds) + while self.checkIfNotEmpty() and self.timerUtil.timesNotUp(): + pass + return (self.timerUtil.timedout, self.getLsOutput()) + + def checkIfEmpty(self): + if len(self.getLsOutput()) > 1: + self.wasEmpty = False + return False + else: + self.wasEmpty = True + return True + + def checkIfNotEmpty(self): + return not self.checkIfEmpty() + + def checkIfContains(self, substring): + if self.getLsOutput().find(substring) > -1: + return True + else: + return False + + def getLsOutput(self): + self.lsOutput = str(Popen(["ls", self.dirPath],bufsize=-1, stdout=PIPE).communicate()[0]) + return self.lsOutput \ No newline at end of file diff --git a/Script/UnitTests/FunctionalTests/FuncTestUtils/__init__.py b/Script/UnitTests/FunctionalTests/FuncTestUtils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Script/UnitTests/FunctionalTests/LocalCopy_Test.py b/Script/UnitTests/FunctionalTests/LocalCopy_Test.py new file mode 100644 index 0000000000000000000000000000000000000000..b3a56bc7064201b422e0e8f8761817f717bc0cd9 --- /dev/null +++ b/Script/UnitTests/FunctionalTests/LocalCopy_Test.py @@ -0,0 +1,114 @@ +import unittest +import signal +import shutil +from subprocess import Popen + +if __name__ == '__main__': + import sys + from os.path import dirname, abspath, join + # add CastorScript/Script to path so imports keeps working + SCRIPT_DIR = abspath(join(dirname(__file__), '../..')) + sys.path.append(SCRIPT_DIR) + +from UnitTests.FunctionalTests.FuncTestUtils.DirUtils import DirectoryChecker + +from UnitTests.FunctionalTests.FuncTestUtils.DirUtils import createTmpDir + + +# I'm a developer, I want to see if the script can copy files. +class TestCanCopyFilesLocally(unittest.TestCase): + + @classmethod + def setUpClass(self): + + # I create test directories and remember their locations + ## returns a dictionary over dirs like src des log config etc.. + self.dirs = createTmpDir("/Script/UnitTests/FunctionalTests") + + ## Create test file + test_file = open('{}/file.data'.format(self.dirs["src"]), "w") + test_file.close() + + # I make a config file from the template (CastorScript/Configs) + # I find the config file + # I open it + cfgTemplate = open(self.dirs["config"] + "/template.cfg", "r") + + # I make a new file in CastorScript/tmp directory + cfgTest = open(self.dirs["tmp"] + "/funcTestConf.cfg", "w") + + # And I copy the content and replaces the directories with my own dirs + custom_cfg_lines = { + "LogDir: ": "LogDir: \'{}\'\n".format(self.dirs["log"]), + "DirList: ": "DirList: [\'{}\',]\n".format(self.dirs["src"]), + "CopyDir: ": "CopyDir: \'{}\'\n".format(self.dirs["des"]), + } + + # And I copy the content and replaces the directories with my own dirs + for line in cfgTemplate: + for key, value in custom_cfg_lines.iteritems(): + if line.find(key) is not -1: + line = value + cfgTest.write(line) + cfgTest.close() + + ## Run the script with config + scriptcommand = ["python", self.dirs["project"] + "/Script/CastorScript.py", self.dirs["tmp"] + "/funcTestConf.cfg" ] + self.castorscriptprocess = Popen(scriptcommand) + + + @classmethod + def tearDownClass(self): + # stop process + self.castorscriptprocess.send_signal(signal.SIGUSR2) + self.castorscriptprocess.wait() + + # remove directories and files + shutil.rmtree(self.dirs["tmp"]) + + + # Test cases: + + def test_localCopy_script_has_been_instanciated(self): + #test if the script runs by Instance + self.assertNotEqual(self.castorscriptprocess, None, msg="Castorscript, is not running; Subprocess is of type none") + + + def test_localCopy_can_produce_logfiles(self): + + seconds = 10 + timeout, output = DirectoryChecker(self.dirs["log"]).waitForAnyFile(seconds) + + timeoutMessage = "Timeout, found no log files within a timeframe of " + str(seconds) + " seconds" + self.assertFalse(timeout, msg=timeoutMessage) + + outNotFoundMessage = "No files ending with .out found in log directory, found:" + output + self.assertTrue(output.find(".out") > -1, msg=outNotFoundMessage) + + + def test_localCopy_can_copy_files_to_des_directory(self): + + seconds = 4 + timeout, output = DirectoryChecker(self.dirs["des"]).waitForAnyFile(seconds) + + timeoutMessage = "Timeout, found no files within a timeframe of " + str(seconds) + " seconds" + self.assertFalse(timeout, msg=timeoutMessage) + + dataNotFoundMessage = "No files ending with .data found in des directory, found:" + output + self.assertTrue(output.find(".data") > -1, msg=dataNotFoundMessage) + + + def test_localCopy_src_files_was_deleted(self): + + seconds = 5 + timeout, output = DirectoryChecker(self.dirs["src"]).waitForEmptyDir(seconds) + + timeoutMessage = "Timeout, found no empty dir within a timeframe of " + str(seconds) + " seconds" + self.assertFalse(timeout, msg=timeoutMessage) + + logsNotFoundMessage = "No files ending with .out found in log directory, found:" + output + self.assertTrue(output.find("file") == -1, msg=logsNotFoundMessage) + + +if __name__ == '__main__': + unittest.main() diff --git a/Script/UnitTests/FunctionalTests/__init__.py b/Script/UnitTests/FunctionalTests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Script/UnitTests/Test_Functional.py b/Script/UnitTests/Test_Functional.py deleted file mode 100644 index f7cd6c080c9376f9f4e5fed6b26b46f6e4b1c22f..0000000000000000000000000000000000000000 --- a/Script/UnitTests/Test_Functional.py +++ /dev/null @@ -1,162 +0,0 @@ -import unittest -import os -import signal -import shutil -from subprocess import Popen - -if __name__ == '__main__': - import sys - from os.path import dirname, abspath, join - # add CastorScript/Script to path so imports keeps working - SCRIPT_DIR = abspath(join(dirname(__file__), '..')) - sys.path.append(SCRIPT_DIR) - -from UnitTests.testUtils import DirectoryChecker, findCastorScriptPids - -# I'm a developer, I want to see if the script can copy files. -class TestCanCopyFilesLocally(unittest.TestCase): - - @classmethod - def setUpClass(self): - self.timeout = False - # kill all processes of CastorScript - # kill the processes based on PID - for pid in findCastorScriptPids(): - os.kill(int(pid), signal.SIGTERM) - - # Define folder structure - self.thisdir = os.path.dirname(__file__) - - self.tmp = "tmp" - - self.src = self.tmp + "/src" - self.des = self.tmp + "/des" - self.log = self.tmp + "/log" - - self.tmpPath = os.path.abspath(os.path.join(self.thisdir, self.tmp)) - - self.srcPath = os.path.abspath(os.path.join(self.thisdir, self.src)) - self.desPath = os.path.abspath(os.path.join(self.thisdir, self.des)) - self.logPath = os.path.abspath(os.path.join(self.thisdir, self.log)) - - # Setup the testcase tools - self.logDirUtil = DirectoryChecker(self.logPath) - self.srcDirUtil = DirectoryChecker(self.srcPath) - self.desDirUtil = DirectoryChecker(self.desPath) - - # Setup the timers - self.timeToTimeout = 5 - self.timerUtil = None - - # I create some folders - try: - os.makedirs(self.srcPath) - os.makedirs(self.desPath) - os.makedirs(self.logPath) - except: - shutil.rmtree(self.tmpPath) - os.makedirs(self.srcPath) - os.makedirs(self.desPath) - os.makedirs(self.logPath) - - # And I populate only the src folder with files to copy - for x in range(1): - open(self.srcPath + '/file' + str(x) + '.data', "w").close() - #open(self.desPath + '/file' + str(x) + '.data', "w").close() - #open(self.logPath + '/file' + str(x) + '.data', "w").close() - - # I make a config file from the template (CastorScript/Configs) - # I find the config file - self.confTemplatePath = os.path.abspath(os.path.join(self.thisdir, "../../Configs")) - - # I open it - cfgTemplate = open(self.confTemplatePath + "/template.cfg", "r") - - # I make a new file in CastorScript/tmp directory - cfgTest = open(self.tmpPath + "/" + "funcTestConf.cfg", "w") - - # And I copy the content and replaces the directories with my own dirs - for line in cfgTemplate: - - if line.find("LogDir: ") > -1: - cfgTest.write("LogDir: \'" + self.logPath + "\'\n") - #print(line) - elif line.find("DirList: ") > -1: - cfgTest.write("DirList: [\'" + self.srcPath + "\',]\n") - #print(line) - elif line.find("CopyDir: ") > -1: - cfgTest.write("CopyDir: \'" + self.desPath + "\'\n") - #print(line) - else: - cfgTest.write(line) - #print(line) - - cfgTest.close() - - # Run the script with config - #NON SHELL - - #self.castorscriptprocess = Popen(scriptcommand) - scriptcommand = ["python", self.thisdir + "/../CastorScript.py", self.tmpPath + "/funcTestConf.cfg" ] - self.castorscriptprocess = Popen(scriptcommand) - #Run in shell - #self.castorscriptprocess = Popen("python " + self.thisdir + "/../CastorScript.py " + self.tmpPath + "/funcTestConf.cfg", shell=True) - # To kill the process for debugging - #self.castorscriptprocess.kill() - - @classmethod - def tearDownClass(self): - # stop process - self.castorscriptprocess.send_signal(signal.SIGUSR2) - self.castorscriptprocess.wait() - - # remove directories and files - shutil.rmtree(self.tmpPath) - - - # Test cases: - - def test_A_cs_should_be_instanciated(self): - #test if the script runs by Instance - self.assertNotEqual(self.castorscriptprocess, None, msg="Castorscript, is not running; Subprocess is of type none") - - def test_B_cs_should_be_running_and_have_a_pid(self): - #test if the script runs by PID - self.assertIsNot(len(findCastorScriptPids()), 0, msg="No CastorScript PIDs found, so must not be running") - - def test_C_cs_should_produce_logfiles(self): - self.timeToTimeout = 10 - self.logDirUtil.waitForAnyFile(self.timeToTimeout) - self.timeout = self.logDirUtil.timerUtil.timedout - output = self.logDirUtil.lsOutput - timeoutMessage = "Timeout, found no log files within a timeframe of " + str(self.timeToTimeout) + " seconds" - self.assertFalse(self.timeout, msg=timeoutMessage) - - outNotFoundMessage = "No files ending with .out found in log directory, found:" + output - self.assertTrue(output.find(".out") > -1, msg=outNotFoundMessage) - - def test_D_cs_should_copy_files_to_des_directory(self): - self.timeToTimeout = 4 - self.desDirUtil.waitForAnyFile(self.timeToTimeout) - self.timeout = self.desDirUtil.timerUtil.timedout - output = self.desDirUtil.lsOutput - timeoutMessage = "Timeout, found no files within a timeframe of " + str(self.timeToTimeout) + " seconds" - self.assertFalse(self.timeout, msg=timeoutMessage) - - dataNotFoundMessage = "No files ending with .data found in des directory, found:" + output - self.assertTrue(output.find(".data") > -1, msg=dataNotFoundMessage) - - def test_E_src_files_should_be_deleted(self): - self.timeToTimeout = 5 - self.srcDirUtil.waitForEmptyDir(self.timeToTimeout) - - self.timeout = self.srcDirUtil.timerUtil.timedout - output = self.srcDirUtil.lsOutput - timeoutMessage = "Timeout, found no empty dir within a timeframe of " + str(self.timeToTimeout) + " seconds" - - self.assertFalse(self.timeout, msg=timeoutMessage) - self.assertTrue(output.find("file") == -1, msg="No files ending with .out found in log directory, found:" + output) - - -if __name__ == '__main__': - unittest.main() diff --git a/Script/UnitTests/testUtils.py b/Script/UnitTests/testUtils.py deleted file mode 100644 index 786ab39e86542b9d0d29a5b6aeaaefd6aea8c11a..0000000000000000000000000000000000000000 --- a/Script/UnitTests/testUtils.py +++ /dev/null @@ -1,86 +0,0 @@ - -from subprocess import Popen, PIPE -import time - -#For timing timeouts -class Timer(): - def __init__(self, seconds): - self.startTime = time.time() - self.endTime = self.startTime + seconds - self.timedout = False - - def timesUp(self): - if (self.endTime < time.time()): - self.timedout = True - return True - else: - return False - - def timesNotUp(self): - return not self.timesUp() - -#For checking a dir -class DirectoryChecker(): - def __init__(self, dirPath): - self.dirPath = dirPath - self.lsOutput = None - self.wasEmpty = None - self.timerUtil = None - - def waitForAnyFile(self, seconds): - timeToTimeout = seconds - self.timerUtil = Timer(timeToTimeout) - while self.checkIfEmpty() and self.timerUtil.timesNotUp(): - pass - - def waitForEmptyDir(self, seconds): - timeToTimeout = seconds - self.timerUtil = Timer(timeToTimeout) - while self.checkIfNotEmpty() and self.timerUtil.timesNotUp(): - pass - - def checkIfEmpty(self): - if len(self.getLsOutput()) > 1: - self.wasEmpty = False - return False - else: - self.wasEmpty = True - return True - - def checkIfNotEmpty(self): - return not self.checkIfEmpty() - - def checkIfContains(self, substring): - if self.getLsOutput().find(substring) > -1: - return True - else: - return False - - def getLsOutput(self): - self.lsOutput = str(Popen(["ls", self.dirPath],bufsize=-1, stdout=PIPE).communicate()[0]) - return self.lsOutput - -#Could be refactored -def findCastorScriptPids(): - # first find all running instances with this command - # ps aux | grep CastorScript.py | grep -v grep | awk '{print $2}' - - # it get list of processes - psCommand = Popen(["ps", "aux"], stdout=PIPE) - - # finds castorscript processes - grepCommand = Popen(["grep", "CastorScript.py"], stdin=psCommand.stdout, stdout=PIPE) - psCommand.stdout.close() - - # sorts grep out of the mix - grepvCommand = Popen(["grep", "-v", "grep"], stdin=grepCommand.stdout, stdout=PIPE) - grepCommand.stdout.close() - - # and finally gets the PIDs - awkCommand = Popen(["awk", "{print $2}"], stdin=grepvCommand.stdout, stdout=PIPE) - grepvCommand.stdout.close() - - pids = str(awkCommand.communicate()[0]).splitlines() - awkCommand.stdout.close() - - return pids \ No newline at end of file diff --git a/run_unit_tests.sh b/run_unit_tests.sh index a26a2fd36c300ebf985e584b32ce35e7fe6e93be..fe6e78adbaf045a7fa82b31e73d91fffde9d600f 100755 --- a/run_unit_tests.sh +++ b/run_unit_tests.sh @@ -10,7 +10,9 @@ then source /afs/cern.ch/atlas/project/tdaq/cmake/cmake_tdaq/bin/cm_setup.sh nightly fi -python -m unittest discover -t $TOPDIR -s $STARTDIR -p "Test_*.py" -f --verbose +#python -m unittest discover -t $TOPDIR -s $STARTDIR -p "*Test.py" -f --verbose +python Script/TestSuite_UnitTests.py +python Script/TestSuite_Functional.py -#for Gitlab CI: -# python -m unittest discover --top-level-directory "Script/" --start-directory "Script/UnitTests/" --pattern "Test_*.py" --verbose \ No newline at end of file +#for Gitlab CI we just find all files in unittest dir, as we just care if it fails or not, the setup above is only for local testing +# python -m unittest discover --top-level-directory "Script/" --start-directory "Script/UnitTests/" --pattern "*Test.py" --verbose \ No newline at end of file