def testPrintCurrentRevisionSyntaxError( self ): iface = BuildScriptInterface( BuildScriptInterfaceTests.SyntaxErrorBuildScriptName ) try: iface.queryCurrentRevision() self.fail( 'The syntax error build script should throw an exception when querying the current revision.' ) except MomError: pass
def testQueryBuildNameSyntaxError( self ): iface = BuildScriptInterface( BuildScriptInterfaceTests.SyntaxErrorBuildScriptName ) try: iface.querySetting( Settings.ScriptBuildName ) self.fail( 'The syntax error build script should throw an exception when printing the build name.' ) except MomError: pass
def testPrintRevisionsSinceSyntaxError( self ): iface = BuildScriptInterface( BuildScriptInterfaceTests.SyntaxErrorBuildScriptName ) try: iface.queryRevisionsSince( '8c758c1f1de2bcc19bda516f1acadf869ba28ee4' ) self.fail( 'The syntax error build script should throw an exception when querying recent revisions.' ) except MomError: pass
def registerNewRevisions( self, buildScript ): '''Determines new revisions committed since the last call with the same build script, and adds those to the database.''' iface = BuildScriptInterface( buildScript ) buildName = iface.querySetting( Settings.ScriptBuildName ) newestBuildInfo = self.getNewestBuildInfo( buildScript ) if newestBuildInfo: revision = newestBuildInfo.getRevision() mApp().debugN( self, 2, 'newest known revision for build script "{0}" ({1}) is "{2}"' .format( buildScript, buildName, revision ) ) buildInfos = self.getBuildInfoForRevisionsSince( buildScript, buildName, revision ) if buildInfos: mApp().message( self, 'build script "{0}" ({1}):'.format( buildScript, buildName ) ) for buildInfo in buildInfos: msg = 'new revision "{0}"'.format( buildInfo.getRevision() ) mApp().message( self, msg ) self.saveBuildInfo( buildInfos ) else: mApp().debug( self, 'no new revisions found for build script "{0}" ({1})' .format( buildScript, buildName ) ) else: buildInfo = self.getBuildInfoForInitialRevision( buildScript, buildName ) mApp().debug( self, 'saving initial revision "{0}" for build script "{1}" ({2})' .format( buildInfo.getRevision(), buildScript, buildName ) ) self.saveBuildInfo( [ buildInfo ] )
def runBuildScriptTestBuild( self, buildScripts ): '''Execute a test build for every build script. If enabled on the command line arguments, SimpleCI calls every build script once, and then exits.''' error = False caughtException = False for script in buildScripts: iface = BuildScriptInterface( script ) name = iface.querySetting( Settings.ScriptBuildName ) buildInfo = self.getBuildStatus().getBuildInfoForInitialRevision( script, name ) buildInfo.setBuildType( 's' ) try: if self.getBuildStatus().performBuild( buildInfo ): self.message( self, 'build script test run finished successfully for "{0}".'.format( script ) ) else: self.message( self, 'build script test run finished with an error for "{0}".'.format( script ) ) error = True except MomError: self.message( self, 'build script test run triggered an exception for "{0}"'.format( script ) ) caughtException = True if caughtException: raise MomError( 'exception during build script test runs.' ) elif error: self.registerReturnCode( 1 ) else: pass
def takeBuildInfoAndBuild( self, buildScripts ): '''Take a new revision from the build job list. Mark it as pending, and build it. Mark it as done afterwards.''' buildInfo = None # get the build names of the build scripts: buildNames = {} for buildScript in buildScripts: iface = BuildScriptInterface( buildScript ) buildName = iface.querySetting( Settings.ScriptBuildName ) if buildName: buildNames[ buildName ] = buildScript else: # this should not happen, since it was checked before mApp().debug( self, 'build script {0} is broken, ignoring.'.format( buildScript ) ) with self.getConnection() as conn: buildInfos = self._loadBuildInfo( conn, BuildInfo.Status.NewRevision ) for build in buildInfos: # the list is ordered by priority if build.getProjectName() in buildNames: build.setBuildStatus( BuildInfo.Status.Pending ) self._updateBuildInfo( conn, build ) buildInfo = build break if not buildInfo: return False try: self.performBuild( buildInfo ) finally: with self.getConnection() as conn: buildInfo.setBuildStatus( BuildInfo.Status.Completed ) self._updateBuildInfo( conn, buildInfo ) return True
def getBuildInfoForInitialRevision( self, buildScript, projectName ): iface = BuildScriptInterface( buildScript ) revision = iface.queryCurrentRevision() buildInfo = BuildInfo() buildInfo.setProjectName( projectName ) buildInfo.setBuildStatus( BuildInfo.Status.InitialRevision ) buildInfo.setRevision( revision ) buildInfo.setBuildScript( buildScript ) return buildInfo
def getRevisionsSinceForBranchBuilds( self, command, options, location, branch, tag ): path, tempdirs = self.fetchBuildScript() with TempFolderDeleter( tempdirs ): iface = BuildScriptInterface( path ) buildName = iface.querySetting( Settings.ScriptBuildName ) mApp().getSettings().set( Settings.ScriptBuildName, buildName ) scm = getScm( location ) scm.setParseBranchCommits( True ) scm._handlePrintCommands( command, options )
def testQueryCharmProjectName( self ): buildDirectory = "charm_build" if os.path.isdir( buildDirectory ): self.fail( "Stale directory exists before the test starts: {0}".format( buildDirectory ) ) iface = BuildScriptInterface( CharmBuildTests.BuildScriptName ) projectNameQueryResult = iface.querySetting( Settings.ProjectName ) self.assertTrue( projectNameQueryResult ) # FIXME make the base dir queryable, so that we do not rely on a hardcoded name # the base directory should *NOT* have been created during the query: self.assertTrue( not os.path.isdir( "charm_build" ) )
class BuildScriptInterfaceTests( MomTestCase ): ThisFilePath = os.path.realpath( os.path.dirname( __file__ ) ) BuildScriptName = os.path.join( ThisFilePath, '..', 'buildscripts', 'example_mom_buildscript.py' ) SyntaxErrorBuildScriptName = os.path.join( ThisFilePath, '..', 'buildscripts', 'syntax_error.py' ) def setUp( self ): MomTestCase.setUp( self ) self.iface = BuildScriptInterface( BuildScriptInterfaceTests.BuildScriptName ) def tearDown( self ): MomTestCase.tearDown( self ) rmtree( "make-o-matic" ) def testQuerySetting( self ): variable = self.iface.querySetting( Settings.MomVersionNumber ) self.assertEquals( variable, mApp().getSettings().get( Settings.MomVersionNumber ) ) def testPrintCurrentRevision( self ): variable = self.iface.queryCurrentRevision() self.assertTrue( variable ) def testPrintRevisionsSince( self ): revisions = self.iface.queryRevisionsSince( '8c758c1f1de2bcc19bda516f1acadf869ba28ee4' ) self.assertTrue( len( revisions ) >= 5 ) def testExecuteBuildScript( self ): runner = self.iface.execute( buildType = 'c', revision = 'HEAD', captureOutput = True ) self.assertEqual( 0, runner.getReturnCode() ) def testQueryBuildNameSyntaxError( self ): iface = BuildScriptInterface( BuildScriptInterfaceTests.SyntaxErrorBuildScriptName ) try: iface.querySetting( Settings.ScriptBuildName ) self.fail( 'The syntax error build script should throw an exception when printing the build name.' ) except MomError: pass def testPrintRevisionsSinceSyntaxError( self ): iface = BuildScriptInterface( BuildScriptInterfaceTests.SyntaxErrorBuildScriptName ) try: iface.queryRevisionsSince( '8c758c1f1de2bcc19bda516f1acadf869ba28ee4' ) self.fail( 'The syntax error build script should throw an exception when querying recent revisions.' ) except MomError: pass def testPrintCurrentRevisionSyntaxError( self ): iface = BuildScriptInterface( BuildScriptInterfaceTests.SyntaxErrorBuildScriptName ) try: iface.queryCurrentRevision() self.fail( 'The syntax error build script should throw an exception when querying the current revision.' ) except MomError: pass
def getNewestBuildInfo( self, buildScript ): iface = BuildScriptInterface( buildScript ) buildName = iface.querySetting( Settings.ScriptBuildName ) connection = self.getConnection() try: cursor = connection.cursor() query = 'select * from {0} where build_name=? order by id desc limit 1'.format( BuildStatus.TableName ) cursor.execute( query, [ buildName ] ) for row in cursor: buildInfo = self.__makeBuildInfoFromRow( row ) return buildInfo # only the first (and only) result is interesting finally: cursor.close()
def checkBuildScripts( self, buildScripts ): '''Verify that the build scripts are working as expected. The method checks that the build script can be called with basic parameters. @return all build scripts that passed the test ''' buildNames = [] goodScripts = [] for buildScript in buildScripts: iface = BuildScriptInterface( buildScript ) try: name = iface.querySetting( Settings.ScriptBuildName ) if name and name not in buildNames: buildNames.append( name ) goodScripts.append( buildScript ) else: self.error( self, 'Error in build script "{0}": The build name "{1}" is already used by another ' 'build script. Build script disregarded.'.format( buildScript, name ) ) except MomError, e: self.error( self, 'Error in build script "{0}": Error querying the build name. Build script disregarded. Reason: {1}' .format( buildScript, e ) )
def getBuildInfoForRevisionsSince( self, buildScript, projectName, revision ): '''Return all revisions that modified the project since the specified revision. @return a list of BuildInfo object, with the latest commit last @throws MomEception, if any of the operations fail ''' iface = BuildScriptInterface( buildScript ) buildInfos = [] lines = iface.queryRevisionsSince( revision ) for line in lines: line = line.strip() if not line: continue buildInfo = BuildInfo() buildInfo.initializeFromPrintableRepresentation( line ) buildInfo.setBuildStatus( buildInfo.Status.NewRevision ) buildInfo.setBuildScript( buildScript ) buildInfos.append( buildInfo ) buildInfos.reverse() return buildInfos
def setUp( self ): MomTestCase.setUp( self ) self.iface = BuildScriptInterface( BuildScriptInterfaceTests.BuildScriptName )
def performBuild( self, buildInfo ): """Start a build process for a new revision. baseDir is the directory where all builds go. To build different revisions and build types under it, subdirectories have to be used.""" buildType = buildInfo.getBuildType().lower() # Under windows we have the problem that paths need to be short, so take only 7 digists of the git hash # this is also done for svn revision numbers, but these should not be so long if sys.platform == 'win32': rev = buildInfo.getRevision()[0:7] else: rev = buildInfo.getRevision() name = make_foldername_from_string( buildInfo.getProjectName() ) # find suitable names for the different build dirs: baseDir = os.path.join( os.getcwd(), 'builds' ) buildRoot = mApp().getSettings().get( Settings.SimpleCIBuildDirectory, required = False ) or baseDir subfolder = make_foldername_from_string( rev ) directory = os.path.normpath( os.path.join( buildRoot, name, buildType, subfolder ) ) # prepare build directory: if os.path.isdir( directory ): mApp().debug( self, 'found remainders of a previous build, nuking it...' ) try: rmtree( directory ) mApp().debug( self, '...that was good!' ) except ( OSError, IOError ) as e: raise ConfigurationError( 'Remnants of a previous build exist at "{0}" and cannot be deleted, bad. Reason: {1}.' .format( directory, e ) ) try: os.makedirs( directory ) except ( OSError, IOError )as e: raise ConfigurationError( 'Cannot create required build directory "{0}"!'.format( directory ) ) mApp().message( self, 'starting build job for project "{0}" at revision {1}.' .format( buildInfo.getProjectName(), rev ) ) with EnvironmentSaver(): os.chdir( directory ) extend_debug_prefix( buildInfo.getProjectName() ) iface = BuildScriptInterface( os.path.abspath( buildInfo.getBuildScript() ) ) runner = iface.executeBuildInfo( buildInfo ) try: with open( 'buildscript.log', 'w' ) as f: text = runner.getStdOutAsString() f.write( text.decode() ) except Exception as e: mApp().message( self, 'Problem! saving the build script output failed during handling an exception! {0}' .format( e ) ) if runner.getReturnCode() != 0: mApp().message( self, 'build failed for project "{0}" at revision {1}'.format( buildInfo.getProjectName(), rev ) ) # FIXME send out email reports on configuration or MOM errors mApp().message( self, 'exit code {0}'.format( runner.getReturnCode() ) ) print( """\ --> ____ _ _ _ _____ _ _ _ --> | __ ) _ _(_) | __| | | ___|_ _(_) | ___ __| | --> | _ \| | | | | |/ _` | | |_ / _` | | |/ _ \/ _` | --> | |_) | |_| | | | (_| | | _| (_| | | | __/ (_| | --> |____/ \__,_|_|_|\__,_| |_| \__,_|_|_|\___|\__,_| --> """ ) return False else: mApp().message( self, 'build succeeded for project "{0}" at revision {1}'.format( buildInfo.getProjectName(), rev ) ) print( """\ --> _ _ _ _ _ --> | |__ _ _(_) |__| | __| |___ _ _ ___ --> | '_ \ || | | / _` | / _` / _ \ ' \/ -_) --> |_.__/\_,_|_|_\__,_| \__,_\___/_||_\___| --> """ ) return True
def invokeBuild( self, args, timeout = None ): path, tempdirs = self.fetchBuildScript() with TempFolderDeleter( tempdirs ): iface = BuildScriptInterface( path ) runner = iface.executeWithArgs( timeout = timeout, args = args ) return runner