1import random 2import sys 3 4from nose.tools import eq_, ok_, assert_true, assert_false, assert_equal 5import fudge 6from fudge import Fake, with_fakes, patched_context 7 8from fabric import decorators, tasks 9from fabric.state import env 10import fabric # for patching fabric.state.xxx 11from fabric.tasks import _parallel_tasks, requires_parallel, execute 12from fabric.context_managers import lcd, settings, hide 13 14from mock_streams import mock_streams 15 16 17# 18# Support 19# 20 21def fake_function(*args, **kwargs): 22 """ 23 Returns a ``fudge.Fake`` exhibiting function-like attributes. 24 25 Passes in all args/kwargs to the ``fudge.Fake`` constructor. However, if 26 ``callable`` or ``expect_call`` kwargs are not given, ``callable`` will be 27 set to True by default. 28 """ 29 # Must define __name__ to be compatible with function wrapping mechanisms 30 # like @wraps(). 31 if 'callable' not in kwargs and 'expect_call' not in kwargs: 32 kwargs['callable'] = True 33 return Fake(*args, **kwargs).has_attr(__name__='fake') 34 35 36 37# 38# @task 39# 40 41def test_task_returns_an_instance_of_wrappedfunctask_object(): 42 def foo(): 43 pass 44 task = decorators.task(foo) 45 ok_(isinstance(task, tasks.WrappedCallableTask)) 46 47 48def test_task_will_invoke_provided_class(): 49 def foo(): pass 50 fake = Fake() 51 fake.expects("__init__").with_args(foo) 52 fudge.clear_calls() 53 fudge.clear_expectations() 54 55 foo = decorators.task(foo, task_class=fake) 56 57 fudge.verify() 58 59 60def test_task_passes_args_to_the_task_class(): 61 random_vars = ("some text", random.randint(100, 200)) 62 def foo(): pass 63 64 fake = Fake() 65 fake.expects("__init__").with_args(foo, *random_vars) 66 fudge.clear_calls() 67 fudge.clear_expectations() 68 69 foo = decorators.task(foo, task_class=fake, *random_vars) 70 fudge.verify() 71 72 73def test_passes_kwargs_to_the_task_class(): 74 random_vars = { 75 "msg": "some text", 76 "number": random.randint(100, 200), 77 } 78 def foo(): pass 79 80 fake = Fake() 81 fake.expects("__init__").with_args(foo, **random_vars) 82 fudge.clear_calls() 83 fudge.clear_expectations() 84 85 foo = decorators.task(foo, task_class=fake, **random_vars) 86 fudge.verify() 87 88 89def test_integration_tests_for_invoked_decorator_with_no_args(): 90 r = random.randint(100, 200) 91 @decorators.task() 92 def foo(): 93 return r 94 95 eq_(r, foo()) 96 97 98def test_integration_tests_for_decorator(): 99 r = random.randint(100, 200) 100 @decorators.task(task_class=tasks.WrappedCallableTask) 101 def foo(): 102 return r 103 104 eq_(r, foo()) 105 106 107def test_original_non_invoked_style_task(): 108 r = random.randint(100, 200) 109 @decorators.task 110 def foo(): 111 return r 112 113 eq_(r, foo()) 114 115 116 117# 118# @runs_once 119# 120 121@with_fakes 122def test_runs_once_runs_only_once(): 123 """ 124 @runs_once prevents decorated func from running >1 time 125 """ 126 func = fake_function(expect_call=True).times_called(1) 127 task = decorators.runs_once(func) 128 for i in range(2): 129 task() 130 131 132def test_runs_once_returns_same_value_each_run(): 133 """ 134 @runs_once memoizes return value of decorated func 135 """ 136 return_value = "foo" 137 task = decorators.runs_once(fake_function().returns(return_value)) 138 for i in range(2): 139 eq_(task(), return_value) 140 141 142@decorators.runs_once 143def single_run(): 144 pass 145 146def test_runs_once(): 147 assert_false(hasattr(single_run, 'return_value')) 148 single_run() 149 assert_true(hasattr(single_run, 'return_value')) 150 assert_equal(None, single_run()) 151 152 153 154# 155# @serial / @parallel 156# 157 158 159@decorators.serial 160def serial(): 161 pass 162 163@decorators.serial 164@decorators.parallel 165def serial2(): 166 pass 167 168@decorators.parallel 169@decorators.serial 170def serial3(): 171 pass 172 173@decorators.parallel 174def parallel(): 175 pass 176 177@decorators.parallel(pool_size=20) 178def parallel2(): 179 pass 180 181fake_tasks = { 182 'serial': serial, 183 'serial2': serial2, 184 'serial3': serial3, 185 'parallel': parallel, 186 'parallel2': parallel2, 187} 188 189def parallel_task_helper(actual_tasks, expected): 190 commands_to_run = map(lambda x: [x], actual_tasks) 191 with patched_context(fabric.state, 'commands', fake_tasks): 192 eq_(_parallel_tasks(commands_to_run), expected) 193 194def test_parallel_tasks(): 195 for desc, task_names, expected in ( 196 ("One @serial-decorated task == no parallelism", 197 ['serial'], False), 198 ("One @parallel-decorated task == parallelism", 199 ['parallel'], True), 200 ("One @parallel-decorated and one @serial-decorated task == paralellism", 201 ['parallel', 'serial'], True), 202 ("Tasks decorated with both @serial and @parallel count as @parallel", 203 ['serial2', 'serial3'], True) 204 ): 205 parallel_task_helper.description = desc 206 yield parallel_task_helper, task_names, expected 207 del parallel_task_helper.description 208 209def test_parallel_wins_vs_serial(): 210 """ 211 @parallel takes precedence over @serial when both are used on one task 212 """ 213 ok_(requires_parallel(serial2)) 214 ok_(requires_parallel(serial3)) 215 216@mock_streams('stdout') 217def test_global_parallel_honors_runs_once(): 218 """ 219 fab -P (or env.parallel) should honor @runs_once 220 """ 221 @decorators.runs_once 222 def mytask(): 223 print("yolo") # 'Carpe diem' for stupid people! 224 with settings(hide('everything'), parallel=True): 225 execute(mytask, hosts=['localhost', '127.0.0.1']) 226 result = sys.stdout.getvalue() 227 eq_(result, "yolo\n") 228 assert result != "yolo\nyolo\n" 229 230 231# 232# @roles 233# 234 235@decorators.roles('test') 236def use_roles(): 237 pass 238 239def test_roles(): 240 assert_true(hasattr(use_roles, 'roles')) 241 assert_equal(use_roles.roles, ['test']) 242 243 244 245# 246# @hosts 247# 248 249@decorators.hosts('test') 250def use_hosts(): 251 pass 252 253def test_hosts(): 254 assert_true(hasattr(use_hosts, 'hosts')) 255 assert_equal(use_hosts.hosts, ['test']) 256 257 258 259# 260# @with_settings 261# 262 263def test_with_settings_passes_env_vars_into_decorated_function(): 264 env.value = True 265 random_return = random.randint(1000, 2000) 266 def some_task(): 267 return env.value 268 decorated_task = decorators.with_settings(value=random_return)(some_task) 269 ok_(some_task(), msg="sanity check") 270 eq_(random_return, decorated_task()) 271 272def test_with_settings_with_other_context_managers(): 273 """ 274 with_settings() should take other context managers, and use them with other 275 overrided key/value pairs. 276 """ 277 env.testval1 = "outer 1" 278 prev_lcwd = env.lcwd 279 280 def some_task(): 281 eq_(env.testval1, "inner 1") 282 ok_(env.lcwd.endswith("here")) # Should be the side-effect of adding cd to settings 283 284 decorated_task = decorators.with_settings( 285 lcd("here"), 286 testval1="inner 1" 287 )(some_task) 288 decorated_task() 289 290 ok_(env.testval1, "outer 1") 291 eq_(env.lcwd, prev_lcwd) 292