| OLD | NEW |
| (Empty) |
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 # that can be found in the LICENSE file. | |
| 4 | |
| 5 from recipe_engine import recipe_api, config | |
| 6 | |
| 7 DEPS = [ | |
| 8 'context', | |
| 9 'path', | |
| 10 'properties', | |
| 11 'step', | |
| 12 ] | |
| 13 | |
| 14 | |
| 15 RETURN_SCHEMA = config.ReturnSchema( | |
| 16 test_me=config.Single(int) | |
| 17 ) | |
| 18 | |
| 19 | |
| 20 PROPERTIES = { | |
| 21 'bad_return': recipe_api.Property(default=False), | |
| 22 'access_invalid_data': recipe_api.Property(default=False), | |
| 23 'timeout': recipe_api.Property(default=0, kind=int), | |
| 24 } | |
| 25 | |
| 26 | |
| 27 def RunSteps(api, bad_return, access_invalid_data, timeout): | |
| 28 if bad_return: | |
| 29 return RETURN_SCHEMA.new(test_me='this should fail') | |
| 30 elif timeout: | |
| 31 # Timeout causes the recipe engine to raise an exception if your step takes | |
| 32 # longer to run than you allow. Units are seconds. | |
| 33 if timeout == 1: | |
| 34 api.step('timeout', ['sleep', '20'], timeout=1) | |
| 35 elif timeout == 2: | |
| 36 try: | |
| 37 api.step('caught timeout', ['sleep', '20'], timeout=1) | |
| 38 except api.step.StepTimeout: | |
| 39 return RETURN_SCHEMA(test_me=4) | |
| 40 | |
| 41 | |
| 42 # TODO(martinis) change this | |
| 43 # The api.step object is directly callable. | |
| 44 api.step('hello', ['echo', 'Hello World']) | |
| 45 api.step('hello', ['echo', 'Why hello, there.']) | |
| 46 | |
| 47 # You can change the current working directory as well | |
| 48 api.step('mk subdir', ['mkdir', '-p', 'something']) | |
| 49 with api.step.context({'cwd': api.path['start_dir'].join('something')}): | |
| 50 api.step('something', ['bash', '-c', 'echo Why hello, there, in a subdir.']) | |
| 51 | |
| 52 # By default, all steps run in 'start_dir', or the cwd of the recipe engine | |
| 53 # when the recipe begins. Because of this, setting cwd to start_dir doesn't | |
| 54 # show anything in particular in the expectations. | |
| 55 with api.step.context({'cwd': api.path['start_dir']}): | |
| 56 api.step('start_dir ignored', ['bash', '-c', 'echo what happen']) | |
| 57 | |
| 58 # You can also manipulate various aspects of the step, such as env. | |
| 59 # These are passed straight through to subprocess.Popen. | |
| 60 # Also, abusing bash -c in this way is a TERRIBLE IDEA DON'T DO IT. | |
| 61 with api.context(env={'friend': 'Darth Vader'}): | |
| 62 api.step('goodbye', ['bash', '-c', 'echo Good bye, $friend.']) | |
| 63 | |
| 64 # You can modify environment in terms of old environment. Environment | |
| 65 # variables are substituted in for expressions of the form %(VARNAME)s. | |
| 66 with api.context(env={'PATH': api.path.pathsep.join( | |
| 67 [str(api.step.package_repo_resource()), '%(PATH)s'])}): | |
| 68 api.step('recipes help', ['recipes.py', '--help']) | |
| 69 | |
| 70 # Finally, you can make your step accept any return code | |
| 71 api.step('anything is cool', ['bash', '-c', 'exit 3'], | |
| 72 ok_ret='any') | |
| 73 | |
| 74 # We can manipulate the step presentation arbitrarily until we run | |
| 75 # the next step. | |
| 76 step_result = api.step('hello', ['echo', 'hello']) | |
| 77 step_result.presentation.status = api.step.EXCEPTION | |
| 78 step_result.presentation.logs['the reason'] = ['The reason\nit failed'] | |
| 79 | |
| 80 # Without a command, a step can be used to present some data from the recipe. | |
| 81 step_result = api.step('Just print stuff', cmd=None) | |
| 82 step_result.presentation.logs['more'] = ['More stuff'] | |
| 83 | |
| 84 try: | |
| 85 api.step('goodbye', ['echo', 'goodbye']) | |
| 86 # Modifying step_result now would raise an AssertionError. | |
| 87 except api.step.StepFailure: | |
| 88 # Raising anything besides StepFailure or StepWarning causes the build to go | |
| 89 # purple. | |
| 90 raise ValueError('goodbye must exit 0!') | |
| 91 | |
| 92 try: | |
| 93 api.step('warning', ['echo', 'warning']) | |
| 94 except api.step.StepFailure as e: | |
| 95 e.result.presentation.status = api.step.WARNING | |
| 96 raise api.step.StepWarning(e.message) | |
| 97 | |
| 98 | |
| 99 # Aggregate failures from tests! | |
| 100 try: | |
| 101 with recipe_api.defer_results(): | |
| 102 api.step('testa', ['echo', 'testa']) | |
| 103 api.step('testb', ['echo', 'testb']) | |
| 104 except recipe_api.AggregatedStepFailure as f: | |
| 105 raise api.step.StepFailure("You can catch step failures.") | |
| 106 | |
| 107 # Some steps are needed from an infrastructure point of view. If these | |
| 108 # steps fail, the build stops, but doesn't get turned red because it's | |
| 109 # not the developers' fault. | |
| 110 try: | |
| 111 api.step('cleanup', ['echo', 'cleaning', 'up', 'build'], infra_step=True) | |
| 112 except api.step.InfraFailure as f: | |
| 113 assert f.result.presentation.status == api.step.EXCEPTION | |
| 114 | |
| 115 # Run a step through a made-up wrapper program. | |
| 116 api.step('application', ['echo', 'main', 'application'], | |
| 117 wrapper=['python', '-c', 'import sys; print sys.argv']) | |
| 118 | |
| 119 if access_invalid_data: | |
| 120 result = api.step('no-op', ['echo', 'I', 'do', 'nothing']) | |
| 121 # Trying to access non-existent attributes on the result should raise. | |
| 122 _ = result.json.output | |
| 123 | |
| 124 return RETURN_SCHEMA(test_me=3) | |
| 125 | |
| 126 | |
| 127 def GenTests(api): | |
| 128 yield ( | |
| 129 api.test('basic') + | |
| 130 api.step_data('anything is cool', retcode=3) | |
| 131 ) | |
| 132 | |
| 133 # If you don't have the expect_exception in this test, you will get something | |
| 134 # like this output. | |
| 135 # ====================================================================== | |
| 136 # ERROR: step:example.exceptional (..../exceptional.json) | |
| 137 # ---------------------------------------------------------------------- | |
| 138 # Traceback (most recent call last): | |
| 139 # <full stack trace ommitted> | |
| 140 # File "annotated_run.py", line 537, in run | |
| 141 # retcode = steps_function(api) | |
| 142 # File "recipe_modules/step/example.py", line 39, in RunSteps | |
| 143 # raise ValueError('goodbye must exit 0!') | |
| 144 # ValueError: goodbye must exit 0! | |
| 145 | |
| 146 yield ( | |
| 147 api.test('exceptional') + | |
| 148 api.step_data('goodbye (2)', retcode=1) + | |
| 149 api.expect_exception('ValueError') | |
| 150 ) | |
| 151 | |
| 152 yield ( | |
| 153 api.test('warning') + | |
| 154 api.step_data('warning', retcode=1) | |
| 155 ) | |
| 156 | |
| 157 yield ( | |
| 158 api.test('defer_results') + | |
| 159 api.step_data('testa', retcode=1) | |
| 160 ) | |
| 161 | |
| 162 yield ( | |
| 163 api.test('invalid_access') + | |
| 164 api.properties(access_invalid_data=True) + | |
| 165 api.expect_exception('StepDataAttributeError') | |
| 166 ) | |
| 167 | |
| 168 yield ( | |
| 169 api.test('infra_failure') + | |
| 170 api.step_data('cleanup', retcode=1) | |
| 171 ) | |
| 172 | |
| 173 yield ( | |
| 174 api.test('bad_return') + | |
| 175 api.properties(bad_return=True) + | |
| 176 api.expect_exception('TypeError') | |
| 177 ) | |
| 178 | |
| 179 yield ( | |
| 180 api.test('timeout') + | |
| 181 api.properties(timeout=1) + | |
| 182 api.step_data('timeout', times_out_after=20) | |
| 183 ) | |
| 184 | |
| 185 yield ( | |
| 186 api.test('catch_timeout') + | |
| 187 api.properties(timeout=2) + | |
| 188 api.step_data('caught timeout', times_out_after=20) | |
| 189 ) | |
| OLD | NEW |