@ -1,194 +1,43 @@
import difflib
import filecmp
import io
import os
import shutil
import tempfile
import yaml
import jenkins
import testtools
from jenkins_jobs import cmd
from jenkins_jobs . cli import entry
from jenkins_jobs . errors import JenkinsJobsException
from mock import patch
from tests . base import mock
from tests . cmd . test_cmd import CmdTestsBase
from tests . cmd . test_recurse_path import fake_os_walk
os_walk_return_values = {
' /jjb_projects ' : [
( ' /jjb_projects ' , ( [ ' dir1 ' , ' dir2 ' , ' dir3 ' ] , ( ) ) ) ,
( ' /jjb_projects/dir1 ' , ( [ ' bar ' ] , ( ) ) ) ,
( ' /jjb_projects/dir2 ' , ( [ ' baz ' ] , ( ) ) ) ,
( ' /jjb_projects/dir3 ' , ( [ ] , ( ) ) ) ,
( ' /jjb_projects/dir1/bar ' , ( [ ] , ( ) ) ) ,
( ' /jjb_projects/dir2/baz ' , ( [ ] , ( ) ) ) ,
] ,
' /jjb_templates ' : [
( ' /jjb_templates ' , ( [ ' dir1 ' , ' dir2 ' , ' dir3 ' ] , ( ) ) ) ,
( ' /jjb_templates/dir1 ' , ( [ ' bar ' ] , ( ) ) ) ,
( ' /jjb_templates/dir2 ' , ( [ ' baz ' ] , ( ) ) ) ,
( ' /jjb_templates/dir3 ' , ( [ ] , ( ) ) ) ,
( ' /jjb_templates/dir1/bar ' , ( [ ] , ( ) ) ) ,
( ' /jjb_templates/dir2/baz ' , ( [ ] , ( ) ) ) ,
] ,
' /jjb_macros ' : [
( ' /jjb_macros ' , ( [ ' dir1 ' , ' dir2 ' , ' dir3 ' ] , ( ) ) ) ,
( ' /jjb_macros/dir1 ' , ( [ ' bar ' ] , ( ) ) ) ,
( ' /jjb_macros/dir2 ' , ( [ ' baz ' ] , ( ) ) ) ,
( ' /jjb_macros/dir3 ' , ( [ ] , ( ) ) ) ,
( ' /jjb_macros/dir1/bar ' , ( [ ] , ( ) ) ) ,
( ' /jjb_macros/dir2/baz ' , ( [ ] , ( ) ) ) ,
] ,
}
def os_walk_side_effects ( path_name , topdown ) :
return fake_os_walk ( os_walk_return_values [ path_name ] ) ( path_name , topdown )
@mock.patch ( ' jenkins_jobs.builder.Jenkins.get_plugins_info ' , mock . MagicMock )
class TestConfigs ( CmdTestsBase ) :
def test_use_global_config ( self ) :
"""
Verify that JJB uses the global config file by default
"""
args = self . parser . parse_args ( [ ' test ' , ' foo ' ] )
self . assertEqual ( cmd . get_config_file ( args ) ,
' /etc/jenkins_jobs/jenkins_jobs.ini ' )
def test_use_config_in_user_home ( self ) :
"""
Verify that JJB uses config file in user home folder
"""
args = self . parser . parse_args ( [ ' test ' , ' foo ' ] )
# args.output_dir = mock.MagicMock()
# mock_isfile.side_effect = True
expected_loc = os . path . join ( os . path . expanduser ( ' ~ ' ) , ' .config ' ,
' jenkins_jobs ' , ' jenkins_jobs.ini ' )
with patch ( ' os.path.isfile ' , return_value = True ) :
self . assertEqual ( cmd . get_config_file ( args ) , expected_loc )
@mock.patch ( ' jenkins_jobs.builder.Jenkins.get_plugins_info ' , mock . MagicMock )
class TestTests ( CmdTestsBase ) :
def test_non_existing_config_dir ( self ) :
"""
Run test mode and pass a non - existing configuration directory
"""
args = self . parser . parse_args ( [ ' test ' , ' foo ' ] )
args . output_dir = mock . MagicMock ( )
self . assertRaises ( IOError , cmd . execute , args , self . config )
def test_non_existing_config_file ( self ) :
"""
Run test mode and pass a non - existing configuration file
"""
args = self . parser . parse_args ( [ ' test ' , ' non-existing.yaml ' ] )
args . output_dir = mock . MagicMock ( )
self . assertRaises ( IOError , cmd . execute , args , self . config )
def test_non_existing_job ( self ) :
"""
Run test mode and pass a non - existing job name
( probably better to fail here )
"""
args = self . parser . parse_args ( [ ' test ' ,
os . path . join ( self . fixtures_path ,
' cmd-001.yaml ' ) ,
' invalid ' ] )
args . output_dir = mock . MagicMock ( wraps = io . BytesIO ( ) )
cmd . execute ( args , self . config ) # probably better to fail here
args = [ ' --conf ' , self . default_config_file , ' test ' ,
os . path . join ( self . fixtures_path ,
' cmd-001.yaml ' ) ,
' invalid ' ]
self . execute_jenkins_jobs_with_args ( args )
def test_valid_job ( self ) :
"""
Run test mode and pass a valid job name
"""
args = self . parser . parse_args ( [ ' test ' ,
os . path . join ( self . fixtures_path ,
' cmd-001.yaml ' ) ,
' foo-job ' ] )
args . output_dir = mock . Mock ( wraps = io . BytesIO ( ) )
cmd . execute ( args , self . config ) # probably better to fail here
@mock.patch ( ' jenkins_jobs.cmd.Builder.update_jobs ' )
def test_multi_path ( self , update_jobs_mock ) :
"""
Run test mode and pass multiple paths .
"""
path_list = list ( os_walk_return_values . keys ( ) )
multipath = os . pathsep . join ( path_list )
args = self . parser . parse_args ( [ ' test ' , multipath ] )
args . output_dir = mock . MagicMock ( )
cmd . execute ( args , self . config )
self . assertEqual ( args . path , path_list )
update_jobs_mock . assert_called_with ( path_list , [ ] ,
output = args . output_dir ,
n_workers = mock . ANY )
@mock.patch ( ' jenkins_jobs.cmd.Builder.update_jobs ' )
@mock.patch ( ' jenkins_jobs.cmd.os.path.isdir ' )
@mock.patch ( ' jenkins_jobs.cmd.os.walk ' )
def test_recursive_multi_path ( self , os_walk_mock , isdir_mock ,
update_jobs_mock ) :
"""
Run test mode and pass multiple paths with recursive path option .
"""
os_walk_mock . side_effect = os_walk_side_effects
isdir_mock . return_value = True
path_list = os_walk_return_values . keys ( )
paths = [ ]
for path in path_list :
paths . extend ( [ p for p , _ in os_walk_return_values [ path ] ] )
multipath = os . pathsep . join ( path_list )
args = self . parser . parse_args ( [ ' test ' , ' -r ' , multipath ] )
args . output_dir = mock . MagicMock ( )
cmd . execute ( args , self . config )
update_jobs_mock . assert_called_with ( paths , [ ] , output = args . output_dir ,
n_workers = mock . ANY )
args = self . parser . parse_args ( [ ' test ' , multipath ] )
args . output_dir = mock . MagicMock ( )
self . config . set ( ' job_builder ' , ' recursive ' , ' True ' )
cmd . execute ( args , self . config )
update_jobs_mock . assert_called_with ( paths , [ ] , output = args . output_dir ,
n_workers = mock . ANY )
@mock.patch ( ' jenkins_jobs.cmd.Builder.update_jobs ' )
@mock.patch ( ' jenkins_jobs.cmd.os.path.isdir ' )
@mock.patch ( ' jenkins_jobs.cmd.os.walk ' )
def test_recursive_multi_path_with_excludes ( self , os_walk_mock , isdir_mock ,
update_jobs_mock ) :
"""
Run test mode and pass multiple paths with recursive path option .
"""
os_walk_mock . side_effect = os_walk_side_effects
isdir_mock . return_value = True
path_list = os_walk_return_values . keys ( )
paths = [ ]
for path in path_list :
paths . extend ( [ p for p , __ in os_walk_return_values [ path ]
if ' dir1 ' not in p and ' dir2 ' not in p ] )
multipath = os . pathsep . join ( path_list )
args = self . parser . parse_args ( [ ' test ' , ' -r ' , multipath , ' -x ' ,
' dir1:dir2 ' ] )
args . output_dir = mock . MagicMock ( )
cmd . execute ( args , self . config )
update_jobs_mock . assert_called_with ( paths , [ ] , output = args . output_dir ,
n_workers = mock . ANY )
args = [ ' --conf ' , self . default_config_file , ' test ' ,
os . path . join ( self . fixtures_path ,
' cmd-001.yaml ' ) ,
' foo-job ' ]
self . execute_jenkins_jobs_with_args ( args )
def test_console_output ( self ) :
"""
@ -197,8 +46,9 @@ class TestTests(CmdTestsBase):
console_out = io . BytesIO ( )
with mock . patch ( ' sys.stdout ' , console_out ) :
cmd . main ( [ ' test ' , os . path . join ( self . fixtures_path ,
' cmd-001.yaml ' ) ] )
args = [ ' --conf ' , self . default_config_file , ' test ' ,
os . path . join ( self . fixtures_path , ' cmd-001.yaml ' ) ]
self . execute_jenkins_jobs_with_args ( args )
xml_content = io . open ( os . path . join ( self . fixtures_path , ' cmd-001.xml ' ) ,
' r ' , encoding = ' utf-8 ' ) . read ( )
self . assertEqual ( console_out . getvalue ( ) . decode ( ' utf-8 ' ) , xml_content )
@ -214,7 +64,8 @@ class TestTests(CmdTestsBase):
with io . open ( input_file , ' r ' ) as f :
with mock . patch ( ' sys.stdout ' , console_out ) :
with mock . patch ( ' sys.stdin ' , f ) :
cmd . main ( [ ' test ' ] )
args = [ ' --conf ' , self . default_config_file , ' test ' ]
self . execute_jenkins_jobs_with_args ( args )
xml_content = io . open ( os . path . join ( self . fixtures_path , ' cmd-001.xml ' ) ,
' r ' , encoding = ' utf-8 ' ) . read ( )
@ -233,7 +84,8 @@ class TestTests(CmdTestsBase):
with io . open ( input_file , ' r ' ) as f :
with mock . patch ( ' sys.stdout ' , console_out ) :
with mock . patch ( ' sys.stdin ' , f ) :
cmd . main ( [ ' test ' ] )
args = [ ' --conf ' , self . default_config_file , ' test ' ]
self . execute_jenkins_jobs_with_args ( args )
xml_content = io . open ( os . path . join ( self . fixtures_path , ' cmd-001.xml ' ) ,
' r ' , encoding = ' utf-8 ' ) . read ( )
@ -253,24 +105,11 @@ class TestTests(CmdTestsBase):
with io . open ( input_file , ' r ' , encoding = ' utf-8 ' ) as f :
with mock . patch ( ' sys.stdout ' , console_out ) :
with mock . patch ( ' sys.stdin ' , f ) :
e = self . assertRaises ( UnicodeError , cmd . main , [ ' test ' ] )
args = [ ' --conf ' , self . default_config_file , ' test ' ]
jenkins_jobs = entry . JenkinsJobs ( args )
e = self . assertRaises ( UnicodeError , jenkins_jobs . execute )
self . assertIn ( " ' ascii ' codec can ' t encode character " , str ( e ) )
def test_config_with_test ( self ) :
"""
Run test mode and pass a config file
"""
args = self . parser . parse_args ( [ ' --conf ' ,
os . path . join ( self . fixtures_path ,
' cmd-001.conf ' ) ,
' test ' ,
os . path . join ( self . fixtures_path ,
' cmd-001.yaml ' ) ,
' foo-job ' ] )
config = cmd . setup_config_settings ( args )
self . assertEqual ( config . get ( ' jenkins ' , ' url ' ) ,
" http://test-jenkins.with.non.default.url:8080/ " )
@mock.patch ( ' jenkins_jobs.builder.YamlParser.generateXML ' )
@mock.patch ( ' jenkins_jobs.parser.ModuleRegistry ' )
def test_plugins_info_stub_option ( self , registry_mock , generateXML_mock ) :
@ -285,16 +124,15 @@ class TestTests(CmdTestsBase):
' -p ' ,
plugins_info_stub_yaml_file ,
os . path . join ( self . fixtures_path , ' cmd-001.yaml ' ) ]
args = self . parser . parse_args ( args )
with mock . patch ( ' sys.stdout ' ) :
cmd . execute ( args , self . config ) # probably better to fail here
self . execute_jenkins_jobs_with_args ( args )
with io . open ( plugins_info_stub_yaml_file ,
' r ' , encoding = ' utf-8 ' ) as yaml_file :
plugins_info_list = yaml . load ( yaml_file )
registry_mock . assert_called_with ( self . config , plugins_info_list )
registry_mock . assert_called_with ( mock . ANY ,
plugins_info_list )
@mock.patch ( ' jenkins_jobs.builder.YamlParser.generateXML ' )
@mock.patch ( ' jenkins_jobs.parser.ModuleRegistry ' )
@ -312,11 +150,10 @@ class TestTests(CmdTestsBase):
' -p ' ,
plugins_info_stub_yaml_file ,
os . path . join ( self . fixtures_path , ' cmd-001.yaml ' ) ]
args = self . parser . parse_args ( args )
with mock . patch ( ' sys.stdout ' ) :
e = self . assertRaises ( JenkinsJobsException , cmd . execute ,
args , self . config )
jenkins_jobs = entry . JenkinsJobs ( args )
e = self . assertRaises ( JenkinsJobsException , jenkins_jobs . execute )
self . assertIn ( " must contain a Yaml list " , str ( e ) )
@ -341,8 +178,9 @@ class TestJenkinsGetPluginInfoError(CmdTestsBase):
jenkins . JenkinsException ( " Connection refused " )
with mock . patch ( ' sys.stdout ' ) :
try :
cmd . main ( [ ' test ' , os . path . join ( self . fixtures_path ,
' cmd-001.yaml ' ) ] )
args = [ ' --conf ' , self . default_config_file , ' test ' ,
os . path . join ( self . fixtures_path , ' cmd-001.yaml ' ) ]
self . execute_jenkins_jobs_with_args ( args )
except jenkins . JenkinsException :
self . fail ( " jenkins.JenkinsException propagated to main " )
except :
@ -356,8 +194,9 @@ class TestJenkinsGetPluginInfoError(CmdTestsBase):
plugins will be skipped when run if no config file provided .
"""
with mock . patch ( ' sys.stdout ' , new_callable = io . BytesIO ) :
cmd . main ( [ ' test ' , os . path . join ( self . fixtures_path ,
' cmd-001.yaml ' ) ] )
args = [ ' --conf ' , self . default_config_file , ' test ' ,
os . path . join ( self . fixtures_path , ' cmd-001.yaml ' ) ]
entry . JenkinsJobs ( args )
self . assertFalse ( get_plugins_info_mock . called )
@mock.patch ( ' jenkins.Jenkins.get_plugins_info ' )
@ -368,9 +207,143 @@ class TestJenkinsGetPluginInfoError(CmdTestsBase):
querying through a config option .
"""
with mock . patch ( ' sys.stdout ' , new_callable = io . BytesIO ) :
cmd . main ( [ ' --conf ' ,
os . path . join ( self . fixtures_path ,
' disable-query-plugins.conf ' ) ,
' test ' ,
os . path . join ( self . fixtures_path , ' cmd-001.yaml ' ) ] )
args = [ ' --conf ' ,
os . path . join ( self . fixtures_path ,
' disable-query-plugins.conf ' ) ,
' test ' ,
os . path . join ( self . fixtures_path , ' cmd-001.yaml ' ) ]
entry . JenkinsJobs ( args )
self . assertFalse ( get_plugins_info_mock . called )
class MatchesDirMissingFilesMismatch ( object ) :
def __init__ ( self , left_directory , right_directory ) :
self . left_directory = left_directory
self . right_directory = right_directory
def describe ( self ) :
return " {0} and {1} contain different files " . format (
self . left_directory ,
self . right_directory )
def get_details ( self ) :
return { }
class MatchesDirFileContentsMismatch ( object ) :
def __init__ ( self , left_file , right_file ) :
self . left_file = left_file
self . right_file = right_file
def describe ( self ) :
left_contents = open ( self . left_file ) . readlines ( )
right_contents = open ( self . right_file ) . readlines ( )
return " {0} is not equal to {1} : \n {2} " . format (
difflib . unified_diff ( left_contents , right_contents ,
fromfile = self . left_file ,
tofile = self . right_file ) ,
self . left_file ,
self . right_file )
def get_details ( self ) :
return { }
class MatchesDir ( object ) :
def __init__ ( self , directory ) :
self . __directory = directory
self . __files = self . __get_files ( directory )
def __get_files ( self , directory ) :
for root , _ , files in os . walk ( directory ) :
return files
def __str__ ( self , ) :
return " MatchesDir( {0} ) " . format ( self . __dirname )
def match ( self , other_directory ) :
other_files = self . __get_files ( other_directory )
self . __files . sort ( )
other_files . sort ( )
if self . __files != other_files :
return MatchesDirMissingFilesMismatch ( self . __directory ,
other_directory )
for i , file in enumerate ( self . __files ) :
my_file = os . path . join ( self . __directory , file )
other_file = os . path . join ( other_directory , other_files [ i ] )
if not filecmp . cmp ( my_file , other_file ) :
return MatchesDirFileContentsMismatch ( my_file , other_file )
return None
@mock.patch ( ' jenkins_jobs.builder.Jenkins.get_plugins_info ' , mock . MagicMock )
class TestTestsMultiPath ( CmdTestsBase ) :
def setUp ( self ) :
super ( TestTestsMultiPath , self ) . setUp ( )
path_list = [ os . path . join ( self . fixtures_path ,
' multi-path/yamldirs/ ' , p )
for p in [ ' dir1 ' , ' dir2 ' ] ]
self . multipath = os . pathsep . join ( path_list )
self . output_dir = tempfile . mkdtemp ( )
def check_dirs_match ( self , expected_dir ) :
try :
self . assertThat ( self . output_dir , MatchesDir ( expected_dir ) )
except testtools . matchers . MismatchError as e :
raise e
else :
shutil . rmtree ( self . output_dir )
def test_multi_path ( self ) :
"""
Run test mode and pass multiple paths .
"""
args = [ ' --conf ' , self . default_config_file , ' test ' ,
' -o ' , self . output_dir , self . multipath ]
self . execute_jenkins_jobs_with_args ( args )
self . check_dirs_match ( os . path . join ( self . fixtures_path ,
' multi-path/output_simple ' ) )
def test_recursive_multi_path_command_line ( self ) :
"""
Run test mode and pass multiple paths with recursive path option .
"""
args = [ ' --conf ' , self . default_config_file , ' test ' ,
' -o ' , self . output_dir , ' -r ' , self . multipath ]
self . execute_jenkins_jobs_with_args ( args )
self . check_dirs_match ( os . path . join ( self . fixtures_path ,
' multi-path/output_recursive ' ) )
def test_recursive_multi_path_config_file ( self ) :
# test recursive set in configuration file
args = [ ' --conf ' , os . path . join ( self . fixtures_path ,
' multi-path/builder-recursive.ini ' ) ,
' test ' , ' -o ' , self . output_dir , self . multipath ]
self . execute_jenkins_jobs_with_args ( args )
self . check_dirs_match ( os . path . join ( self . fixtures_path ,
' multi-path/output_recursive ' ) )
def test_recursive_multi_path_with_excludes ( self ) :
"""
Run test mode and pass multiple paths with recursive path option .
"""
exclude_path = os . path . join ( self . fixtures_path ,
' multi-path/yamldirs/dir2/dir1 ' )
args = [ ' --conf ' , self . default_config_file , ' test ' ,
' -x ' , exclude_path ,
' -o ' , self . output_dir ,
' -r ' , self . multipath ]
self . execute_jenkins_jobs_with_args ( args )
self . check_dirs_match (
os . path . join ( self . fixtures_path ,
' multi-path/output_recursive_with_excludes ' ) )