diff --git a/.codeclimate.yml b/.codeclimate.yml index d4f0b4687..acabb8b09 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -23,3 +23,4 @@ exclude_patterns: - "game/_version.py" - "versioneer.py" - "game_frontend/djangoBundler.js" + - "aimmo/avatar_examples/**" diff --git a/aimmo-game-worker/Pipfile b/aimmo-game-worker/Pipfile index 385fceb4f..3df21592c 100644 --- a/aimmo-game-worker/Pipfile +++ b/aimmo-game-worker/Pipfile @@ -5,7 +5,8 @@ name = "pypi" [packages] kubernetes = "*" -aimmo-game-creator = {editable = true, path = "."} +aimmo-game-worker = {editable = true, path = "."} +restrictedpython = "==4.0.b7" [requires] python_version = "2.7" diff --git a/aimmo-game-worker/Pipfile.lock b/aimmo-game-worker/Pipfile.lock index cb0422464..467253906 100644 --- a/aimmo-game-worker/Pipfile.lock +++ b/aimmo-game-worker/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "58d4a5d007d207c103524baa2cca08cadcf5f979c0e807c767f0ef10f4a42dd5" + "sha256": "fbae5bb9d29ab27c4476079e4c6d12448eee072cc95784df90c6b69ffd7adfe1" }, "pipfile-spec": 6, "requires": { @@ -18,12 +18,12 @@ "default": { "adal": { "hashes": [ - "sha256:534ab04df7ab7c30bc7fe9526c3120e50b9496982f6c85001b05fd7cf4134eb7", - "sha256:a2a2f7e4a2d2e2014e3d5ff9f6d614af280c879a1dbf96bb64d92d85a814a645" + "sha256:ba52913c38d76b4a4d88eaab41a5763d056ab6d073f106e0605b051ab930f5c1", + "sha256:bf79392b8e9e5e82aa6acac3835ba58bbac0ccf7e15befa215863f83d5f6a007" ], - "version": "==1.1.0" + "version": "==1.2.0" }, - "aimmo-game-creator": { + "aimmo-game-worker": { "editable": true, "path": "." }, @@ -36,17 +36,17 @@ }, "cachetools": { "hashes": [ - "sha256:90f1d559512fc073483fe573ef5ceb39bf6ad3d39edc98dc55178a2b2b176fa3", - "sha256:d1c398969c478d336f767ba02040fa22617333293fb0b8968e79b16028dfee35" + "sha256:0a258d82933a1dd18cb540aca4ac5d5690731e24d1239a08577b814998f49785", + "sha256:4621965b0d9d4c82a79a29edbad19946f5e7702df4afae7d1ed2df951559a8cc" ], - "version": "==2.1.0" + "version": "==3.0.0" }, "certifi": { "hashes": [ - "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", - "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" ], - "version": "==2018.8.24" + "version": "==2018.10.15" }, "cffi": { "hashes": [ @@ -94,34 +94,34 @@ }, "click": { "hashes": [ - "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", - "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" ], - "version": "==6.7" + "version": "==7.0" }, "cryptography": { "hashes": [ - "sha256:02602e1672b62e803e08617ec286041cc453e8d43f093a5f4162095506bc0beb", - "sha256:10b48e848e1edb93c1d3b797c83c72b4c387ab0eb4330aaa26da8049a6cbede0", - "sha256:17db09db9d7c5de130023657be42689d1a5f60502a14f6f745f6f65a6b8195c0", - "sha256:227da3a896df1106b1a69b1e319dce218fa04395e8cc78be7e31ca94c21254bc", - "sha256:2cbaa03ac677db6c821dac3f4cdfd1461a32d0615847eedbb0df54bb7802e1f7", - "sha256:31db8febfc768e4b4bd826750a70c79c99ea423f4697d1dab764eb9f9f849519", - "sha256:4a510d268e55e2e067715d728e4ca6cd26a8e9f1f3d174faf88e6f2cb6b6c395", - "sha256:6a88d9004310a198c474d8a822ee96a6dd6c01efe66facdf17cb692512ae5bc0", - "sha256:76936ec70a9b72eb8c58314c38c55a0336a2b36de0c7ee8fb874a4547cadbd39", - "sha256:7e3b4aecc4040928efa8a7cdaf074e868af32c58ffc9bb77e7bf2c1a16783286", - "sha256:8168bcb08403ef144ff1fb880d416f49e2728101d02aaadfe9645883222c0aa5", - "sha256:8229ceb79a1792823d87779959184a1bf95768e9248c93ae9f97c7a2f60376a1", - "sha256:8a19e9f2fe69f6a44a5c156968d9fc8df56d09798d0c6a34ccc373bb186cee86", - "sha256:8d10113ca826a4c29d5b85b2c4e045ffa8bad74fb525ee0eceb1d38d4c70dfd6", - "sha256:be495b8ec5a939a7605274b6e59fbc35e76f5ad814ae010eb679529671c9e119", - "sha256:dc2d3f3b1548f4d11786616cf0f4415e25b0fbecb8a1d2cd8c07568f13fdde38", - "sha256:e4aecdd9d5a3d06c337894c9a6e2961898d3f64fe54ca920a72234a3de0f9cb3", - "sha256:e79ab4485b99eacb2166f3212218dd858258f374855e1568f728462b0e6ee0d9", - "sha256:f995d3667301e1754c57b04e0bae6f0fa9d710697a9f8d6712e8cca02550910f" - ], - "version": "==2.3.1" + "sha256:02915ee546b42ce513e8167140e9937fc4c81a06a82216e086ccce51f347948a", + "sha256:03cc8bc5a69ae3d44acf1a03facdb7c10a94c67907862c563e10efe72b737977", + "sha256:07f76bde6815c55195f3b3812d35769cc7c765144c0bb71ae45e02535d078591", + "sha256:13eac1c477b9af7e9a9024369468d08aead6ad78ed599d163ad046684474364b", + "sha256:179bfb585c5efc87ae0e665770e4896727b92dbc1f810c761b1ebf8363e2fec8", + "sha256:414af0ba308e74c1f8bc5b11befc86cb66b10be8959547786f64258830d2096f", + "sha256:41a1ca14f255df8c44dd22c6006441d631d1589104045ec7263cc47e9772f41a", + "sha256:54947eb98bc4eef99ddf49f45d2694ea5a3929ab3edc9806ad01967368594d82", + "sha256:5bac7a2abda07d0c3c8429210349bb54149ad8940dc7bcffedcd56519b410a3c", + "sha256:7f41af8c586bed9f59cfe8832d818b3b75c860d7025da9cd2db76875a72ff785", + "sha256:8004fae1b3cb2dbd90a011ad972e49a7e78a871b89c70cc7213cf4ebd2532bcb", + "sha256:8e0eccadc3b465e12c50a5b8fb4d39cf401b44d7bb9936c70fddb5e5aaf740d5", + "sha256:95b4741722269cfdc134fec23b7ae6503ee2aea83d0924cfee6d6ec54cd42d8e", + "sha256:a06f5aa6d7a94531dfe82eb2972e669258c452fe9cf88f76116610de4c789785", + "sha256:b0833d27c7eb536bc27323a1e8e22cb39ebac78c4ef3be0167ba40f447344808", + "sha256:b72dec675bc59a01edc96616cd48ec465b714481caa0938c8bbca5d18f17d5df", + "sha256:c800ddc23b5206ce025f23225fdde89cdc0e64016ad914d5be32d1f602ce9495", + "sha256:c980c8c313a5e014ae12e2245e89e7b30427e5a98cbb88afe478ecae85f3abaa", + "sha256:e85b410885addaeb31a867eabcefc9ef4a7e904ad45eac9e60a763a54b244626" + ], + "version": "==2.4.1" }, "enum34": { "hashes": [ @@ -142,10 +142,10 @@ }, "google-auth": { "hashes": [ - "sha256:9ca363facbf2622d9ba828017536ccca2e0f58bd15e659b52f312172f8815530", - "sha256:a4cf9e803f2176b5de442763bd339b313d3f1ed3002e3e1eb6eec1d7c9bbc9b4" + "sha256:494e747bdc2cdeb0fa6ef85118de2ea1a563f160294cce05048c6ff563fda1bb", + "sha256:b08a27888e9d1c17a891b3688aacc9c6f2019d7f6c5a2e73588e6bb9a2c0fa98" ], - "version": "==1.5.1" + "version": "==1.6.1" }, "idna": { "hashes": [ @@ -159,14 +159,15 @@ "sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", "sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c" ], - "markers": "python_version < '3'", + "markers": "python_version == '2.7'", "version": "==1.0.22" }, "itsdangerous": { "hashes": [ - "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" ], - "version": "==0.24" + "version": "==1.1.0" }, "jinja2": { "hashes": [ @@ -177,17 +178,44 @@ }, "kubernetes": { "hashes": [ - "sha256:5ee6e2e949ca800ad8a73da6f67c2a637c2c803945b006e6105beae83e43b273", - "sha256:84dfb4319afac189e8327b71b9332b5329d2a78074f58958c5f06a870edf32ba" + "sha256:0cc9ce02d838da660efa0a67270b4b7d47e6beb8889673cd45c86f897e2d6821", + "sha256:54f8e7bb1dd9a55cf416dff76a63c4ae441764280942d9913f2243676f29d02c" ], "index": "pypi", - "version": "==7.0.0" + "version": "==8.0.0" }, "markupsafe": { "hashes": [ - "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", + "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", + "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", + "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", + "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", + "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", + "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", + "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", + "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", + "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", + "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", + "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", + "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", + "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", + "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", + "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", + "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", + "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", + "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", + "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", + "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", + "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", + "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", + "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", + "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", + "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", + "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", + "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" ], - "version": "==1.0" + "version": "==1.1.0" }, "oauthlib": { "hashes": [ @@ -212,9 +240,9 @@ }, "pycparser": { "hashes": [ - "sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226" + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], - "version": "==2.18" + "version": "==2.19" }, "pyjwt": { "hashes": [ @@ -225,10 +253,10 @@ }, "python-dateutil": { "hashes": [ - "sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0", - "sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8" + "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93", + "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02" ], - "version": "==2.7.3" + "version": "==2.7.5" }, "pyyaml": { "hashes": [ @@ -248,10 +276,10 @@ }, "requests": { "hashes": [ - "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", - "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", + "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" ], - "version": "==2.19.1" + "version": "==2.20.1" }, "requests-oauthlib": { "hashes": [ @@ -260,12 +288,20 @@ ], "version": "==1.0.0" }, + "restrictedpython": { + "hashes": [ + "sha256:4a59877e3cde0a31f6eb99f73a5be3a629d6b3d43f4d7d71a49fef188745b455", + "sha256:6b4b667d15d9258a38c02f27d68915655397364342d7a1905a4ac4471cd6eec4" + ], + "index": "pypi", + "version": "==4.0b7" + }, "rsa": { "hashes": [ - "sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5", - "sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd" + "sha256:14ba45700ff1ec9eeb206a2ce76b32814958a98e372006c8fb76ba820211be66", + "sha256:1a836406405730121ae9823e19c6e806c62bbad73f890574fff50efa4122c487" ], - "version": "==3.4.2" + "version": "==4.0" }, "six": { "hashes": [ @@ -276,18 +312,17 @@ }, "urllib3": { "hashes": [ - "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", - "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" ], - "markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version < '4' and python_version >= '2.6'", - "version": "==1.23" + "version": "==1.24.1" }, "websocket-client": { "hashes": [ - "sha256:03763384c530b331ec3822d0b52ffdc28c3aeb8a900ac8c98b2ceea3128a7b4e", - "sha256:3c9924675eaf0b27ae22feeeab4741bb4149b94820bd3a143eeaf8b62f64d821" + "sha256:8c8bf2d4f800c3ed952df206b18c28f7070d9e3dcbd6ca6291127574f57ee786", + "sha256:e51562c91ddb8148e791f0155fdb01325d99bb52c4cdbb291aee7a3563fd0849" ], - "version": "==0.52.0" + "version": "==0.54.0" }, "werkzeug": { "hashes": [ diff --git a/aimmo-game-worker/avatar_runner.py b/aimmo-game-worker/avatar_runner.py index 3abd3b0ea..ef6d25d59 100644 --- a/aimmo-game-worker/avatar_runner.py +++ b/aimmo-game-worker/avatar_runner.py @@ -1,15 +1,62 @@ +from __future__ import print_function + import logging import traceback import sys import imp +import inspect +import re + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO -from six import StringIO +import simulation.action as avatar_action +import simulation.direction as direction from simulation.action import WaitAction, Action from user_exceptions import InvalidActionException +from RestrictedPython import compile_restricted, utility_builtins +from RestrictedPython.Guards import safe_builtins, safer_getattr, guarded_setattr, full_write_guard + LOGGER = logging.getLogger(__name__) +try: + import __builtin__ +except ImportError: + raise ImportError + # Python 3 + import builtins as __builtin__ + + +def add_actions_to_globals(): + action_classes = filter(lambda x: x[1].__module__ == "simulation.action", inspect.getmembers(avatar_action, inspect.isclass)) + + for action_class in action_classes: + restricted_globals[action_class[0]] = action_class[1] + + +_getattr_ = safer_getattr +_setattr_ = guarded_setattr +_write_ = full_write_guard +__metaclass__ = type + +restricted_globals = dict(__builtins__=safe_builtins) + +restricted_globals['_getattr_'] = _getattr_ +restricted_globals['_setattr_'] = _setattr_ +restricted_globals['_getiter_'] = list +restricted_globals['_print_'] = print +restricted_globals['_write_'] = _write_ +restricted_globals['__metaclass__'] = __metaclass__ +restricted_globals['__name__'] = "Avatar" + +add_actions_to_globals() +restricted_globals['direction'] = direction +restricted_globals['random'] = utility_builtins['random'] + class AvatarRunner(object): def __init__(self, avatar=None, auto_update=True): @@ -23,8 +70,15 @@ def _avatar_src_changed(self, new_avatar_code): def _get_new_avatar(self, src_code): self.avatar_source_code = src_code + module = imp.new_module('avatar') # Create a temporary module to execute the src_code in - exec src_code in module.__dict__ + module.__dict__.update(restricted_globals) + + byte_code = compile_restricted(src_code, filename='', mode='exec') + exec(byte_code, restricted_globals) + + module.__dict__['Avatar'] = restricted_globals['Avatar'] + return module.Avatar() def _update_avatar(self, src_code): @@ -36,11 +90,8 @@ def _update_avatar(self, src_code): The last condition is necessary because if _get_new_avatar fails the avatar object will not have been updated, meaning that self.avatar will actually be for the last correct code """ - should_update = (self.avatar is None or - self.auto_update and self._avatar_src_changed(src_code) or - not self.update_successful) - if should_update: + if self.should_update(src_code): try: self.avatar = self._get_new_avatar(src_code) except Exception as e: @@ -49,6 +100,10 @@ def _update_avatar(self, src_code): else: self.update_successful = True + def should_update(self, src_code): + return (self.avatar is None or self.auto_update and self._avatar_src_changed(src_code) or + not self.update_successful) + def process_avatar_turn(self, world_map, avatar_state, src_code): output_log = StringIO() avatar_updated = self._avatar_src_changed(src_code) @@ -79,7 +134,8 @@ def process_avatar_turn(self, world_map, avatar_state, src_code): sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ - logs = output_log.getvalue() + logs = self.clean_logs(output_log.getvalue()) + return {'action': action, 'log': logs, 'avatar_updated': avatar_updated} def decide_action(self, world_map, avatar_state): @@ -88,6 +144,13 @@ def decide_action(self, world_map, avatar_state): raise InvalidActionException(action) return action.serialise() + def clean_logs(self, logs): + getattr_pattern = "" + + clean_logs = re.sub(getattr_pattern, '', logs) + + return clean_logs + @staticmethod def get_only_user_traceback(): """ If the traceback does not contain any reference to the user code, found by '', @@ -95,7 +158,7 @@ def get_only_user_traceback(): traceback_list = traceback.format_exc().split('\n') start_of_user_traceback = 0 for i in range(len(traceback_list)): - if '' in traceback_list[i]: + if '' in traceback_list[i]: start_of_user_traceback = i break return traceback_list[start_of_user_traceback:] diff --git a/aimmo-game-worker/test-initialise.py b/aimmo-game-worker/test-initialise.py index 45158d3fd..8aba10c0a 100755 --- a/aimmo-game-worker/test-initialise.py +++ b/aimmo-game-worker/test-initialise.py @@ -8,7 +8,7 @@ if len(sys.argv) > 1: avatar_file = sys.argv[1] else: - avatar_file = '../aimmo/avatar_examples/dumb_avatar.py' + avatar_file = '../aimmo/avatar_examples/simple_avatar.py' with open(avatar_file) as avatar_fileobj: avatar_data = avatar_fileobj.read() diff --git a/aimmo-game-worker/tests/test_avatar_runner.py b/aimmo-game-worker/tests/test_avatar_runner.py index 3f7817100..35ef7f47c 100644 --- a/aimmo-game-worker/tests/test_avatar_runner.py +++ b/aimmo-game-worker/tests/test_avatar_runner.py @@ -13,28 +13,24 @@ class TestAvatarRunner(TestCase): def test_runner_does_not_crash_on_code_errors(self): - class Avatar(object): - def handle_turn(self, world_map, avatar_state): - assert False + avatar = '''class Avatar: + def handle_turn(self, world_map, avatar_state): + assert False''' - runner = AvatarRunner(avatar=Avatar(), auto_update=False) + runner = AvatarRunner(avatar=avatar, auto_update=False) action = runner.process_avatar_turn(world_map={}, avatar_state={}, src_code='')['action'] self.assertEqual(action, {'action_type': 'wait'}) def test_runner_updates_code_on_change(self): - avatar1 = '''class Avatar(object): + avatar1 = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import EAST - return MoveAction(EAST) + return MoveAction(direction.EAST) ''' - avatar2 = '''class Avatar(object): + avatar2 = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import WEST - return MoveAction(WEST) + return MoveAction(direction.WEST) ''' runner = AvatarRunner() @@ -45,44 +41,16 @@ def handle_turn(self, world_map, avatar_state): self.assertEqual(response['action'], {'action_type': 'move', 'options': {'direction': WEST}}) - def test_runner_can_maintain_state(self): - """ This test ensures that if the code is the same, we do not recreate the avatar object in the runner. - We check this by making sure that self.x is being updated and its value retained. """ - - avatar = '''class Avatar(object): - def __init__(self): - from simulation.action import MoveAction - from simulation.direction import NORTH, SOUTH, EAST, WEST - - self.moves = [MoveAction(NORTH), MoveAction(EAST), MoveAction(SOUTH), MoveAction(WEST)] - self.x = 0 - - def handle_turn(self, world_map, avatar_state): - move = self.moves[self.x] - self.x += 1 - return move - ''' - runner = AvatarRunner() - - directions = [NORTH, EAST, SOUTH, WEST] - for direction in directions: - response = runner.process_avatar_turn(world_map={}, avatar_state={}, src_code=avatar) - self.assertEqual(response['action'], {'action_type': 'move', 'options': {'direction': direction}}) - def test_update_code_flag_simple(self): - avatar1 = '''class Avatar(object): + avatar1 = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import NORTH - return MoveAction(NORTH) + return MoveAction(direction.NORTH) ''' - avatar2 = '''class Avatar(object): + avatar2 = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import SOUTH - return MoveAction(SOUTH) + return MoveAction(direction.SOUTH) ''' runner = AvatarRunner() @@ -96,7 +64,7 @@ def handle_turn(self, world_map, avatar_state): self.assertFalse(response['avatar_updated']) def test_update_code_flag_with_syntax_errors(self): - avatar = '''class Avatar(object: + avatar = '''class Avatar pass ''' runner = AvatarRunner() @@ -106,44 +74,48 @@ def test_update_code_flag_with_syntax_errors(self): self.assertFalse(response['avatar_updated']) def test_invalid_action_exception(self): - avatar = '''class Avatar(object): + avatar = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import NORTH - + + new_dir = random.choice(direction.ALL_DIRECTIONS) ''' runner = AvatarRunner() runner._update_avatar(src_code=avatar) with self.assertRaises(InvalidActionException): runner.decide_action(world_map={}, avatar_state={}) + def test_does_not_update_with_imports(self): + avatar = '''class Avatar: + def handle_turn(self, world_map, avatar_state): + import os + return MoveAction(random.choice(direction.ALL_DIRECTIONS)) + ''' + runner = AvatarRunner() + runner._update_avatar(src_code=avatar) + with self.assertRaises(ImportError): + runner.decide_action(world_map={}, avatar_state={}) + def test_updated_successful(self): - avatar_ok = '''class Avatar(object): + avatar_ok = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import NORTH - return MoveAction(NORTH) + return MoveAction(direction.NORTH) ''' - avatar_syntax_error = '''class Avatar(object): + avatar_syntax_error = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import NORTH - return MoveAction(NORTH + return MoveAction(direction.NORTH ''' - avatar_bad_constructor = '''class Avatar(object): + avatar_bad_constructor = '''class Avatar: def __init__(self): return 1 + 'foo' def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import NORTH - return MoveAction(NORTH) + return MoveAction(direction.NORTH) ''' runner = AvatarRunner() @@ -156,27 +128,49 @@ def handle_turn(self, world_map, avatar_state): runner.process_avatar_turn(world_map={}, avatar_state={}, src_code=avatar_ok) self.assertTrue(runner.update_successful) + def test_updates_with_for_loop(self): + avatar = '''class Avatar: + def handle_turn(self, world_map, avatar_state): + x = 0 + for x in range(5): + x = x + 1 + print(x) + + return MoveAction(random.choice(direction.ALL_DIRECTIONS)) + ''' + runner = AvatarRunner() + runner.process_avatar_turn(world_map={}, avatar_state={}, src_code=avatar) + self.assertTrue(runner.update_successful) + + def test_updates_with_inplace_operator(self): + avatar = '''class Avatar: + def handle_turn(self, world_map, avatar_state): + x = 0 + x += 2 + + return MoveAction(random.choice(direction.ALL_DIRECTIONS)) + ''' + runner = AvatarRunner() + runner.process_avatar_turn(world_map={}, avatar_state={}, src_code=avatar) + self.assertTrue(runner.update_successful) + def test_runtime_error_contains_only_user_traceback(self): - avatar = '''class Avatar(object): + avatar = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import NORTH 1 + 'foo' - return MoveAction(NORTH) + return MoveAction(direction.NORTH) ''' runner = AvatarRunner() response = runner.process_avatar_turn(world_map={}, avatar_state={}, src_code=avatar) self.assertFalse('/usr/src/app/' in response['log']) def test_syntax_error_contains_only_user_traceback(self): - avatar = '''class Avatar(object): + avatar = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import NORTH - return MoveAction(NORTH)))) + return MoveAction(direction.NORTH)))) ''' runner = AvatarRunner() @@ -184,18 +178,14 @@ def handle_turn(self, world_map, avatar_state): self.assertFalse('/usr/src/app/' in response['log']) def test_invalid_action_exception_contains_only_user_traceback(self): - avatar1 = '''class Avatar(object): + avatar1 = '''class Avatar def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import NORTH return None ''' - avatar2 = '''class Avatar(object): + avatar2 = '''class Avatar: def handle_turn(self, world_map, avatar_state): - from simulation.action import MoveAction - from simulation.direction import NORTH return 1 diff --git a/aimmo/avatar_examples/attacking_avatar.py b/aimmo/avatar_examples/attacking_avatar.py index 111109834..139b4b761 100644 --- a/aimmo/avatar_examples/attacking_avatar.py +++ b/aimmo/avatar_examples/attacking_avatar.py @@ -1,16 +1,12 @@ -class Avatar(object): +class Avatar: def handle_turn(self, world_state, avatar_state): - from simulation.action import MoveAction - from simulation import direction self.avatar_state = avatar_state - self.location = self.avatar_state.location + directions = (direction.EAST, direction.SOUTH, direction.WEST, direction.NORTH) direction_of_other_avatar = next((d for d in directions if world_state.is_visible(self.location + d) and world_state.get_cell(self.location + d).avatar), None) if direction_of_other_avatar: - from simulation.action import AttackAction return AttackAction(direction_of_other_avatar) - import random direction_to_other_player = self.direction_to(next(cell.location for cell in world_state.all_cells() if cell.avatar and cell.location != avatar_state.location)) if direction_to_other_player: @@ -18,7 +14,6 @@ def handle_turn(self, world_state, avatar_state): return MoveAction(random.choice(directions)) def direction_to(self, location): - from simulation import direction vector_to = location - self.location if vector_to.x != 0: return direction.Direction(1 if vector_to.x > 0 else -1, 0) diff --git a/aimmo/avatar_examples/health_seeker_avatar.py b/aimmo/avatar_examples/health_seeker_avatar.py index 16c95c71f..53fd49afe 100644 --- a/aimmo/avatar_examples/health_seeker_avatar.py +++ b/aimmo/avatar_examples/health_seeker_avatar.py @@ -1,10 +1,5 @@ -class Avatar(object): +class Avatar: def handle_turn(self, world_state, avatar_state): - from simulation.action import MoveAction - from simulation import direction - import random - from simulation.action import WaitAction - self.world_state = world_state self.avatar_state = avatar_state @@ -29,12 +24,11 @@ def get_closest_pickup_location(self): pickup_cells = list(self.world_state.pickup_cells()) if pickup_cells: c = min(pickup_cells, key=lambda cell: self.distance_between(cell.location, self.avatar_state.location)) - print 'targetting', c + print('targetting' + c) return c.location else: return None def get_possible_directions(self): - from simulation import direction directions = (direction.EAST, direction.SOUTH, direction.WEST, direction.NORTH) return [d for d in directions if self.world_state.can_move_to(self.avatar_state.location + d)] diff --git a/aimmo/avatar_examples/dumb_avatar.py b/aimmo/avatar_examples/simple_avatar.py similarity index 50% rename from aimmo/avatar_examples/dumb_avatar.py rename to aimmo/avatar_examples/simple_avatar.py index c89a80b84..4aec36e52 100644 --- a/aimmo/avatar_examples/dumb_avatar.py +++ b/aimmo/avatar_examples/simple_avatar.py @@ -1,8 +1,4 @@ -class Avatar(object): +class Avatar: def handle_turn(self, world_state, avatar_state): - from simulation.action import MoveAction - import simulation.direction as direction - import random - new_dir = random.choice(direction.ALL_DIRECTIONS) return MoveAction(new_dir) diff --git a/aimmo/avatar_examples/winner_avatar.py b/aimmo/avatar_examples/winner_avatar.py index 9df9cb0f1..b117393d0 100644 --- a/aimmo/avatar_examples/winner_avatar.py +++ b/aimmo/avatar_examples/winner_avatar.py @@ -1,10 +1,5 @@ -class Avatar(object): +class Avatar: def handle_turn(self, world_state, avatar_state): - from simulation.action import MoveAction - from simulation import direction - import random - from simulation.action import WaitAction - self.world_state = world_state self.avatar_state = avatar_state @@ -33,6 +28,5 @@ def get_closest_score_location(self): return None def get_possible_directions(self): - from simulation import direction directions = (direction.EAST, direction.SOUTH, direction.WEST, direction.NORTH) return [d for d in directions if self.world_state.can_move_to(self.avatar_state.location + d)] diff --git a/aimmo/views.py b/aimmo/views.py index 3c6113405..c912e08cd 100644 --- a/aimmo/views.py +++ b/aimmo/views.py @@ -44,7 +44,7 @@ def code(request, id): except Avatar.DoesNotExist: initial_code_file_name = os.path.join( os.path.abspath(os.path.dirname(__file__)), - 'avatar_examples/dumb_avatar.py', + 'avatar_examples/simple_avatar.py', ) with open(initial_code_file_name) as initial_code_file: initial_code = initial_code_file.read() diff --git a/setup.py b/setup.py index e43e4efaa..0e1a5e77e 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ 'hypothesis', 'flask-cors >= 3.0, < 3.1', 'psutil >= 5.4, < 5.5', + 'RestrictedPython == 4.0.b7' ], tests_require=[ 'httmock', diff --git a/version.txt b/version.txt index 9325c3ccd..60a2d3e96 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.0 \ No newline at end of file +0.4.0 \ No newline at end of file