From c29663beb19de6a4a9725293110a220a9f15c80b Mon Sep 17 00:00:00 2001 From: Pavell Date: Thu, 31 Jan 2019 14:43:57 +0100 Subject: [PATCH 1/7] fixed git push --- server/serverpl/filebrowser/tests/views/test_git.py | 2 +- server/serverpl/filebrowser/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/serverpl/filebrowser/tests/views/test_git.py b/server/serverpl/filebrowser/tests/views/test_git.py index 33295fb008..a296bf28bc 100644 --- a/server/serverpl/filebrowser/tests/views/test_git.py +++ b/server/serverpl/filebrowser/tests/views/test_git.py @@ -353,7 +353,7 @@ def test_show(self): 'path': 'Yggdrasil/folder1/TPE/function001.pl', }, content_type='application/json' ) - self.assertContains(response, "test\ntest2\n", status_code=200) + self.assertContains(response, "test\ntest2", status_code=200) def test_show_no_path(self): diff --git a/server/serverpl/filebrowser/utils.py b/server/serverpl/filebrowser/utils.py index 4d22e9cb55..f0e76ddf76 100644 --- a/server/serverpl/filebrowser/utils.py +++ b/server/serverpl/filebrowser/utils.py @@ -23,13 +23,13 @@ def rm_fb_root(path): def repository_url(path): """Returns git origin's url of path.""" - return gitcmd.remote_url(path)[1][:-1] + return gitcmd.remote_url(path)[1] def repository_branch(path): """Returns current git's branch of path.""" - return gitcmd.current_branch(path)[1][:-1] + return gitcmd.current_branch(path)[1] From 1eb801016da9ed6e3338f07cf2e68a7a03d2f76b Mon Sep 17 00:00:00 2001 From: CALLE CHRISTOPHE Date: Mon, 4 Feb 2019 11:04:42 +0100 Subject: [PATCH 2/7] fixed test_reload_activity --- server/serverpl/playexo/tests/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/serverpl/playexo/tests/test_models.py b/server/serverpl/playexo/tests/test_models.py index accc26b92d..a30872afdc 100644 --- a/server/serverpl/playexo/tests/test_models.py +++ b/server/serverpl/playexo/tests/test_models.py @@ -83,7 +83,7 @@ def test_create_activity(self): def test_reload_activity(self): activity1 = Activity.objects.create(name="test", pltp=self.pltp) activity2 = Activity.objects.create(name="test", parent=activity1, - pltp=PLTP(sha1="", name="pltp test")) + pltp=PLTP.objects.create(sha1="", name="pltp test")) sessionactivity1 = SessionActivity.objects.create(user=self.user, activity=activity1) sessionactivity2 = SessionActivity.objects.create(user=self.user, activity=activity2) activity1.reload() From 7e75f5cbdeaab7ce82c415180f1c4b17e5c458e8 Mon Sep 17 00:00:00 2001 From: CALLE CHRISTOPHE Date: Mon, 4 Feb 2019 11:28:21 +0100 Subject: [PATCH 3/7] fixed test index --- server/serverpl/loader/models.py | 4 ++++ server/serverpl/loader/tests/test_models.py | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/server/serverpl/loader/models.py b/server/serverpl/loader/models.py index 7571739a1e..06996fc7dd 100644 --- a/server/serverpl/loader/models.py +++ b/server/serverpl/loader/models.py @@ -50,6 +50,10 @@ def delete(self, *args, **kwargs): + ")' has been deleted since it wasn't link to any PLTPs") pl.delete() super(PLTP, self).delete(*args, **kwargs) + + + def indexed_pl(self): + return [i.pl for i in sorted(self.index_set.all(), key=lambda i: i.index)] diff --git a/server/serverpl/loader/tests/test_models.py b/server/serverpl/loader/tests/test_models.py index 21105c4740..b1b86981d0 100644 --- a/server/serverpl/loader/tests/test_models.py +++ b/server/serverpl/loader/tests/test_models.py @@ -17,8 +17,8 @@ def test_index(self): Index.objects.create(pltp=pltp, pl=pl4) Index.objects.create(pltp=pltp, pl=pl3) - self.assertNotEqual(list(pltp.pl.all()), [pl1, pl2, pl3, pl4]) - self.assertEqual(list(pltp.pl.all()), [pl1, pl2, pl4, pl3]) + self.assertNotEqual(pltp.indexed_pl(), [pl1, pl2, pl3, pl4]) + self.assertEqual(pltp.indexed_pl(), [pl1, pl2, pl4, pl3]) def test_index_move_start(self): @@ -32,13 +32,13 @@ def test_index_move_start(self): i2 = Index.objects.create(pltp=pltp, pl=pl2) i3 = Index.objects.create(pltp=pltp, pl=pl3) - self.assertEqual(list(pltp.pl.all()), [pl0, pl1, pl2, pl3]) + self.assertEqual(pltp.indexed_pl(), [pl0, pl1, pl2, pl3]) i0.move_start() - self.assertEqual(list(pltp.pl.all()), [pl0, pl1, pl2, pl3]) + self.assertEqual(pltp.indexed_pl(), [pl0, pl1, pl2, pl3]) i2.move_start() - self.assertEqual(list(pltp.pl.all()), [pl2, pl0, pl1, pl3]) + self.assertEqual(pltp.indexed_pl(), [pl2, pl0, pl1, pl3]) i3.move_start() - self.assertEqual(list(pltp.pl.all()), [pl3, pl2, pl0, pl1]) + self.assertEqual(pltp.indexed_pl(), [pl3, pl2, pl0, pl1]) def test_index_move_end(self): @@ -52,13 +52,13 @@ def test_index_move_end(self): i2 = Index.objects.create(pltp=pltp, pl=pl2) i3 = Index.objects.create(pltp=pltp, pl=pl3) - self.assertEqual(list(pltp.pl.all()), [pl0, pl1, pl2, pl3]) + self.assertEqual(pltp.indexed_pl(), [pl0, pl1, pl2, pl3]) i3.move_end() - self.assertEqual(list(pltp.pl.all()), [pl0, pl1, pl2, pl3]) + self.assertEqual(pltp.indexed_pl(), [pl0, pl1, pl2, pl3]) i1.move_end() - self.assertEqual(list(pltp.pl.all()), [pl0, pl2, pl3, pl1]) + self.assertEqual(pltp.indexed_pl(), [pl0, pl2, pl3, pl1]) i0.move_end() - self.assertEqual(list(pltp.pl.all()), [pl2, pl3, pl1, pl0]) + self.assertEqual(pltp.indexed_pl(), [pl2, pl3, pl1, pl0]) def test_delete(self): From 6e342d242d41e5bca061d18529776068b4f5a7c4 Mon Sep 17 00:00:00 2001 From: pl-test Date: Mon, 4 Feb 2019 12:13:51 +0100 Subject: [PATCH 4/7] updated requirements --- server/serverpl/requirements.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/serverpl/requirements.txt b/server/serverpl/requirements.txt index c45b80e2e6..93b0e294a9 100755 --- a/server/serverpl/requirements.txt +++ b/server/serverpl/requirements.txt @@ -15,7 +15,7 @@ markdown mkdocs mock oauth2 -psycopg2 +psycopg2-binary pydenticon pytest pytest-django @@ -26,10 +26,7 @@ requests sympy whitenoise yattag --e git://github.com/qcoumes/timeout-decorator.git#egg=timeout-decorator -e git://github.com/qcoumes/django-enumfields.git#egg=django-enumfields -e git://github.com/qcoumes/htmlprint.git#egg=htmlprint -e git://github.com/qcoumes/gitcmd.git#egg=gitcmd -e git://github.com/qcoumes/django-markdown.git#egg=django-markdown - - From 198de3ae97ce7f06ce682f02ff9a6f3d90b2daf7 Mon Sep 17 00:00:00 2001 From: CALLE CHRISTOPHE Date: Mon, 4 Feb 2019 12:14:39 +0100 Subject: [PATCH 5/7] fixed test for different dbms --- .../ressources/100/TPE/Dir_test/test.txt | 1 + .../tests/ressources/100/TPE/function001.pl | 27 + .../tests/ressources/100/TPE/operator001.pl | 51 + .../tests/ressources/100/__init__.py | 0 .../filebrowser/tests/ressources/100/carre.pl | 21 + .../tests/ressources/100/editorform.html | 20 + .../100/extract_test/application.tar | Bin 0 -> 3584 bytes .../100/extract_test/application.tar.gz | Bin 0 -> 195 bytes .../100/extract_test/application.tar.xz | Bin 0 -> 224 bytes .../100/extract_test/application.zip | Bin 0 -> 223 bytes .../100/extract_test/application2.zip | Bin 0 -> 464 bytes .../100/extract_test/application3.zip | Bin 0 -> 945 bytes .../100/extract_test/application4.zip | Bin 0 -> 464 bytes .../100/extract_test/applicationmeta.zip | Bin 0 -> 462 bytes .../100/extract_test/applicationpublish.zip | Bin 0 -> 223 bytes .../tests/ressources/100/feedback.py | 94 + .../tests/ressources/100/function001.pl | 27 + .../tests/ressources/100/pldoctest.py | 2880 +++++++++++++++++ .../tests/ressources/100/plgrader.pltp | 65 + .../tests/ressources/100/plgrader.py | 382 +++ .../tests/ressources/100/plgrader2.pltp | 65 + .../tests/ressources/100/plutils.py | 288 ++ .../tests/ressources/100/soluce.pl | 38 + .../tests/ressources/100/template.html | 143 + .../tests/ressources/100/template.pl | 30 + .../filebrowser/tests/ressources/100/text.pl | 27 + .../filebrowser/tests/ressources/100/truc.pl | 0 .../tests/ressources/100/truc2.pltp | 0 .../serverpl/filebrowser/tests/ressources/200 | 1 + .../filebrowser/tests/ressources/300/HEAD | 1 + .../filebrowser/tests/ressources/300/config | 4 + .../tests/ressources/300/description | 1 + .../300/hooks/applypatch-msg.sample | 15 + .../ressources/300/hooks/commit-msg.sample | 24 + .../ressources/300/hooks/post-update.sample | 8 + .../300/hooks/pre-applypatch.sample | 14 + .../ressources/300/hooks/pre-commit.sample | 49 + .../ressources/300/hooks/pre-push.sample | 53 + .../ressources/300/hooks/pre-rebase.sample | 169 + .../ressources/300/hooks/pre-receive.sample | 24 + .../300/hooks/prepare-commit-msg.sample | 36 + .../tests/ressources/300/hooks/update.sample | 128 + .../tests/ressources/300/info/exclude | 6 + .../00/2307c1f4b8ddb273c44f1f7a2f3692f8858c78 | Bin 0 -> 3889 bytes .../09/5a057d4a651ec412d06b59e32e9b02871592d5 | Bin 0 -> 53 bytes .../0d/748883ebea63a3e7fcb393fb148132f1b368fc | Bin 0 -> 241 bytes .../18/0cf8328022becee9aaa2577a8f84ea2b9f3827 | Bin 0 -> 21 bytes .../20/a243ba824ff3f9f36137a79dba04d016f19d21 | Bin 0 -> 172 bytes .../26/1e9b13f23bcddf1366c6278d216a76ee57b0e0 | Bin 0 -> 462 bytes .../29/a4f8abdbedc3f2c51eb33f3ba937f363628f2b | Bin 0 -> 127 bytes .../30/d74d258442c7c65512eafab474568dd706c430 | Bin 0 -> 19 bytes .../31/bf67bd5e951eff2d24ba8578ff781efc87b087 | Bin 0 -> 296 bytes .../34/1d783cf03fb1894f710defb7b673d586ef19ab | 1 + .../34/4e3ebe85880b5bb691a625604a2e78e4f204fb | Bin 0 -> 319 bytes .../3b/63e56b4f753a79d1a4b9e8ebd0f185dfca3774 | Bin 0 -> 3637 bytes .../41/c95ff3d9638ecbda96905ad4283e7f0087c293 | Bin 0 -> 474 bytes .../42/f477d21de6148e2701d6787ccfa9ce0a6be9b3 | Bin 0 -> 209 bytes .../43/c7e2ff4a10563973c2d62ee81a94fbf36d68d6 | Bin 0 -> 146 bytes .../46/35a197f02062cc137577919bfbe23fe6ef96c6 | Bin 0 -> 561 bytes .../5c/2951fa0274d4c97b90876f7e05cfddccbd46ed | Bin 0 -> 127 bytes .../70/cb729495a935102cd6cbe4774770e29cf37fb0 | Bin 0 -> 137 bytes .../71/5dc2fb52ebc112831673ddf4e5a02bd134957f | 2 + .../76/1d782254146333d0dd9cbc7a4f56f7d2b7195f | 1 + .../7e/049dd66cd2ca9c295de1a18fa6a2253fe92ba9 | Bin 0 -> 128 bytes .../94/c8be18a32d8d8d27aefcaca6c4529c3fc0cf5a | Bin 0 -> 145 bytes .../97/4856d34075fb0f7e3fd8df357803cc8b688837 | Bin 0 -> 244 bytes .../9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4 | Bin 0 -> 20 bytes .../a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 | Bin 0 -> 21 bytes .../c2/a65b6d8d988e0e026ab0742c9269b99947736c | Bin 0 -> 176 bytes .../c7/b10c2a80b14bf9ce078a162d2d84d4acb8e216 | Bin 0 -> 33698 bytes .../cf/0f1690be3bbe030f223a63eeeba39ba623f112 | Bin 0 -> 473 bytes .../cf/20eb0628df33ebf39662e12e04e569b991e82c | 1 + .../d7/901a2c4a77a0191f35d94be4b587871455eaad | Bin 0 -> 787 bytes .../d7/b633a2794c7088b1e0054c36c716a358903fc3 | Bin 0 -> 273 bytes .../db/03da4d5980d1bc6a67a24b357c0e46b2c209ba | Bin 0 -> 224 bytes .../db/074db632fe68242ab97f311c5b4afab0be3a3d | Bin 0 -> 474 bytes .../db/2e6e1ba8e6938341cc50f0f084b026600e7351 | Bin 0 -> 295 bytes .../e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 | Bin 0 -> 15 bytes .../f6/4920b72386e414c58444c9b5a5810f94feb890 | Bin 0 -> 1069 bytes .../f9/48dbadaa12e303936318b55c975c0b5d19f729 | Bin 0 -> 215 bytes .../fd/cc54c0b6c0e0bf650c74bf050db54ab760677e | Bin 0 -> 923 bytes .../tests/ressources/300/refs/heads/master | 1 + .../filebrowser/tests/views/test_load_pltp.py | 47 +- 83 files changed, 4721 insertions(+), 24 deletions(-) create mode 100644 server/serverpl/filebrowser/tests/ressources/100/TPE/Dir_test/test.txt create mode 100644 server/serverpl/filebrowser/tests/ressources/100/TPE/function001.pl create mode 100644 server/serverpl/filebrowser/tests/ressources/100/TPE/operator001.pl create mode 100644 server/serverpl/filebrowser/tests/ressources/100/__init__.py create mode 100644 server/serverpl/filebrowser/tests/ressources/100/carre.pl create mode 100644 server/serverpl/filebrowser/tests/ressources/100/editorform.html create mode 100644 server/serverpl/filebrowser/tests/ressources/100/extract_test/application.tar create mode 100644 server/serverpl/filebrowser/tests/ressources/100/extract_test/application.tar.gz create mode 100644 server/serverpl/filebrowser/tests/ressources/100/extract_test/application.tar.xz create mode 100644 server/serverpl/filebrowser/tests/ressources/100/extract_test/application.zip create mode 100644 server/serverpl/filebrowser/tests/ressources/100/extract_test/application2.zip create mode 100644 server/serverpl/filebrowser/tests/ressources/100/extract_test/application3.zip create mode 100644 server/serverpl/filebrowser/tests/ressources/100/extract_test/application4.zip create mode 100644 server/serverpl/filebrowser/tests/ressources/100/extract_test/applicationmeta.zip create mode 100644 server/serverpl/filebrowser/tests/ressources/100/extract_test/applicationpublish.zip create mode 100644 server/serverpl/filebrowser/tests/ressources/100/feedback.py create mode 100644 server/serverpl/filebrowser/tests/ressources/100/function001.pl create mode 100644 server/serverpl/filebrowser/tests/ressources/100/pldoctest.py create mode 100644 server/serverpl/filebrowser/tests/ressources/100/plgrader.pltp create mode 100644 server/serverpl/filebrowser/tests/ressources/100/plgrader.py create mode 100644 server/serverpl/filebrowser/tests/ressources/100/plgrader2.pltp create mode 100644 server/serverpl/filebrowser/tests/ressources/100/plutils.py create mode 100644 server/serverpl/filebrowser/tests/ressources/100/soluce.pl create mode 100644 server/serverpl/filebrowser/tests/ressources/100/template.html create mode 100644 server/serverpl/filebrowser/tests/ressources/100/template.pl create mode 100644 server/serverpl/filebrowser/tests/ressources/100/text.pl create mode 100644 server/serverpl/filebrowser/tests/ressources/100/truc.pl create mode 100644 server/serverpl/filebrowser/tests/ressources/100/truc2.pltp create mode 160000 server/serverpl/filebrowser/tests/ressources/200 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/HEAD create mode 100644 server/serverpl/filebrowser/tests/ressources/300/config create mode 100644 server/serverpl/filebrowser/tests/ressources/300/description create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/applypatch-msg.sample create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/commit-msg.sample create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/post-update.sample create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/pre-applypatch.sample create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/pre-commit.sample create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/pre-push.sample create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/pre-rebase.sample create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/pre-receive.sample create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/prepare-commit-msg.sample create mode 100755 server/serverpl/filebrowser/tests/ressources/300/hooks/update.sample create mode 100644 server/serverpl/filebrowser/tests/ressources/300/info/exclude create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/00/2307c1f4b8ddb273c44f1f7a2f3692f8858c78 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/09/5a057d4a651ec412d06b59e32e9b02871592d5 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/0d/748883ebea63a3e7fcb393fb148132f1b368fc create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/20/a243ba824ff3f9f36137a79dba04d016f19d21 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/26/1e9b13f23bcddf1366c6278d216a76ee57b0e0 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/29/a4f8abdbedc3f2c51eb33f3ba937f363628f2b create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/30/d74d258442c7c65512eafab474568dd706c430 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/31/bf67bd5e951eff2d24ba8578ff781efc87b087 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/34/1d783cf03fb1894f710defb7b673d586ef19ab create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/34/4e3ebe85880b5bb691a625604a2e78e4f204fb create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/3b/63e56b4f753a79d1a4b9e8ebd0f185dfca3774 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/41/c95ff3d9638ecbda96905ad4283e7f0087c293 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/42/f477d21de6148e2701d6787ccfa9ce0a6be9b3 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/43/c7e2ff4a10563973c2d62ee81a94fbf36d68d6 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/46/35a197f02062cc137577919bfbe23fe6ef96c6 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/5c/2951fa0274d4c97b90876f7e05cfddccbd46ed create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/70/cb729495a935102cd6cbe4774770e29cf37fb0 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/71/5dc2fb52ebc112831673ddf4e5a02bd134957f create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/76/1d782254146333d0dd9cbc7a4f56f7d2b7195f create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/7e/049dd66cd2ca9c295de1a18fa6a2253fe92ba9 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/94/c8be18a32d8d8d27aefcaca6c4529c3fc0cf5a create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/97/4856d34075fb0f7e3fd8df357803cc8b688837 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/c2/a65b6d8d988e0e026ab0742c9269b99947736c create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/c7/b10c2a80b14bf9ce078a162d2d84d4acb8e216 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/cf/0f1690be3bbe030f223a63eeeba39ba623f112 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/cf/20eb0628df33ebf39662e12e04e569b991e82c create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/d7/901a2c4a77a0191f35d94be4b587871455eaad create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/d7/b633a2794c7088b1e0054c36c716a358903fc3 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/db/03da4d5980d1bc6a67a24b357c0e46b2c209ba create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/db/074db632fe68242ab97f311c5b4afab0be3a3d create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/db/2e6e1ba8e6938341cc50f0f084b026600e7351 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/f6/4920b72386e414c58444c9b5a5810f94feb890 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/f9/48dbadaa12e303936318b55c975c0b5d19f729 create mode 100644 server/serverpl/filebrowser/tests/ressources/300/objects/fd/cc54c0b6c0e0bf650c74bf050db54ab760677e create mode 100644 server/serverpl/filebrowser/tests/ressources/300/refs/heads/master diff --git a/server/serverpl/filebrowser/tests/ressources/100/TPE/Dir_test/test.txt b/server/serverpl/filebrowser/tests/ressources/100/TPE/Dir_test/test.txt new file mode 100644 index 0000000000..30d74d2584 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/TPE/Dir_test/test.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/server/serverpl/filebrowser/tests/ressources/100/TPE/function001.pl b/server/serverpl/filebrowser/tests/ressources/100/TPE/function001.pl new file mode 100644 index 0000000000..31bf67bd5e --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/TPE/function001.pl @@ -0,0 +1,27 @@ +# Copyright 2016 Dominique Revuz +title=fonction +author=Dominique Revuz +name= Une fonction bob +tag=function +template=../template.pl +text== +# Fonctions + +Ecrire une fonction **bob** qui retourne la valeur entière 1238. + + >>> bob() + 1238 +== + +code== + +# Fin du code, n'écrivez pas de code après cette ligne s'il vous plait ! +# L'équipe PL +== + +pltest== +>>> bob() +1238 +>>> bob()==1238 +True +== diff --git a/server/serverpl/filebrowser/tests/ressources/100/TPE/operator001.pl b/server/serverpl/filebrowser/tests/ressources/100/TPE/operator001.pl new file mode 100644 index 0000000000..715dc2fb52 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/TPE/operator001.pl @@ -0,0 +1,51 @@ +# Copyright 2016 Dominique Revuz +author=Dominique Revuz +name= Boites d'oeufs (Operateurs) +title= Modulo et Diviser +tag= print|input|operator.mod|operator.floordiv +template=../soluce.pl +text== + +# Operator // et % + +**Joelle** a des poules, tous les matins elle ramasse les oeufs et souhaite les mettre dans des boites. + +Quand elle a fini de ramasser les oeufs elle appelle son fils **Emile** en lui donnant le nombre d'oeufs. Il doit calculer le nombre de boites de 6 oeufs et le nombre d'oeufs restants. + +Aidons **Emile** avec // qui est la division entière et % (opérateur modulo) qui calcule le reste de la division entière. + + +== + +code== +nbroeufs = int( input("saisissez le nombre d'oeufs :") ) + +b= # votre opération +r= # votre opération +print("\nPour ",nbroeufs," il faut:") +print( b , "boites,") +print("et il restera ", r , "oeufs.") + +# Fin du code, n'écrivez pas de code après cette ligne s'il vous plait ! +# L'équipe PL +== + +inputgenerator== +from random import randint + +print(randint(10,100)*6+randint(1,6)) +== + +soluce== +nbroeufs = int( input("saisissez le nombre d'oeufs :") ) + +b= nbroeufs // 6 +r= nbroeufs % 6 + +print("\nPour ",nbroeufs," il faut:") +print( b , "boites,") +print("et il restera ", r , "oeufs.") + +== + + diff --git a/server/serverpl/filebrowser/tests/ressources/100/__init__.py b/server/serverpl/filebrowser/tests/ressources/100/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/server/serverpl/filebrowser/tests/ressources/100/carre.pl b/server/serverpl/filebrowser/tests/ressources/100/carre.pl new file mode 100644 index 0000000000..761d782254 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/carre.pl @@ -0,0 +1,21 @@ +# Copyright 2016 Dominique Revuz +author=Dominique Revuz +title=L'éponge Caré +name=L'éponge Carée +tag=function # N'oubliez pas de remplir ce champs svp +template=template.pl +text== +## Une fonction carre ## +Ecrivez une fonction **carre** qui retourne le carré de son paramêtre. +== + +pltest== +>>> carre(510) +260100 +>>> carre(0) +0 +>>> carre(10) +100 +>>> +== + diff --git a/server/serverpl/filebrowser/tests/ressources/100/editorform.html b/server/serverpl/filebrowser/tests/ressources/100/editorform.html new file mode 100644 index 0000000000..4635a197f0 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/editorform.html @@ -0,0 +1,20 @@ +{% load static %} + +
+{% if student_answer__ and student_answer__ != "" %}{{ student_answer__ }}{% else %}{{ code }}{% endif %}
+ +
+ + + + + diff --git a/server/serverpl/filebrowser/tests/ressources/100/extract_test/application.tar b/server/serverpl/filebrowser/tests/ressources/100/extract_test/application.tar new file mode 100644 index 0000000000000000000000000000000000000000..db03da4d5980d1bc6a67a24b357c0e46b2c209ba GIT binary patch literal 3584 zcmeHIOA5j;5bfGigdRXAzX^DoXctDLDXFf+yE~yRZdyTTO2A|@kO`SLZ}ReN*WKLN z-kopnHtesxZ>zNz0H9K=0xqS&7==VlaasZxr8p?$XcPmkHK>Zg+Cb*+d-T*dK?x^+ z@xeE9XX2i4E%2oz?GL?7O|dU0vr0YdIP&^9gx4-Ba0N_q1{xRW{U7Y-8(9Aj_+9@f zP!#FxEfeQRN@6j-@vr~N~a}G5-i^W&4DK+ zpEr+2aZWdq>(iLHz8W7YRFe9e_#ZjKym^c6>|vQAvoi%=>FcIZuj#_ZM0wv2k@1{| za+7A&+>n$shEj^53jsmL;T4Km+JAKYfPy>q3>c+Q`4R0fNZu1&%YZ;A0S{Q&E7gu_ zoe+xF1#{wuP?XNDt)&Exj a0l5Ky8~_0SQ&&q3kq^FlM_oa^Yiqwiu3bUhn>X91P5H=X^SDmn-lBc>+Kzhi(kAnR;2p`FUV-uURTZYhaqYI_#9z zx%0uBngX`QFyOXC7LO&Q+W`p=)`W8?;US1_M@nW9BurM>C`C(Sx*fN36|otG6fUSv z1_cdTaH9naBa<96u1J)CMyCM7Uq=uV$r@0UvO=O1&HMmwkde3|6k;SJ(6A+q?&wA$ zn-BE|&<-qqz-Nan1F{`!@Yq3w6}X}k5i)Nb1%SRmcDX(za)B-fMJ@@Uqln`2Xl%wJ Y*+anftZYD^Gq3{T4WL1XK_Sin0C*PNA^-pY literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/100/extract_test/application4.zip b/server/serverpl/filebrowser/tests/ressources/100/extract_test/application4.zip new file mode 100644 index 0000000000000000000000000000000000000000..42f477d21de6148e2701d6787ccfa9ce0a6be9b3 GIT binary patch literal 464 zcmWIWW@Zs#W?&&q3kq^FlM_oa^Yiqwiu3bUhn>_I< literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/100/extract_test/applicationpublish.zip b/server/serverpl/filebrowser/tests/ressources/100/extract_test/applicationpublish.zip new file mode 100644 index 0000000000000000000000000000000000000000..70cb729495a935102cd6cbe4774770e29cf37fb0 GIT binary patch literal 223 zcmWIWW@Zs#-~htDSu=eYpg;gf3os}!Bo-9pWF{w;Waj7TWfkY=g@*7lu;1D7APR;{ zE4UdLSza(RFo22GVW+gtoe$pB6tFdhA;6oRqvLkD0x#Gw1|Xl2NrV|;8L}M6G8kCW Y2x5_Hb$~Z38%Q4`5XJ%NY!HV50O6E51^@s6 literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/100/feedback.py b/server/serverpl/filebrowser/tests/ressources/100/feedback.py new file mode 100644 index 0000000000..fdcc54c0b6 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/feedback.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# feedback.py +# +# Copyright 2017 Dominique Revuz +# + + + +from jinja2 import Template + + +def subnlbybr(str): + """ +>>> subnlbybr("\\n") +'
' + """ + return "
".join(str.split("\n")) + + + + +class Feedback: + """ + Classe de stockage et de production des sorties d'un exercie pl + """ + def __init__(self): + self.compilation = False # No error yet + self.success = True + self.showinput= False + self.executionhistory = [] + self.feedbacktext="" + self.div=[] + self.asio=False + with open("template.html", "r") as f: + self.template = Template(f.read()) + + + def addInput(self,newinput): + self.executionhistory.append(("input",subnlbybr(newinput))) + + def addOutput(self,newoutput): + self.executionhistory.append(("output",subnlbybr(newoutput))) + def addExpected(self,newoutput): + self.executionhistory.append(("expected",subnlbybr(newoutput))) + def addOptained(self,newoutput): + self.executionhistory.append(("optained",subnlbybr(newoutput))) + def addExpectedOptained(self,newoutput,expected): + self.addExpected(expected) + self.asio =True + self.addOptained(newoutput) + self.success=False + def addCompilationError(self,text): + self.compilationError=subnlbybr(text) + self.compile = True + self.success = False + def addFeedback(self,text): + self.feedbacktext += subnlbybr(text) + + def adddiv(self, text, indice): + self.div.append((indice,subnlbybr(text))) + + def addsymbole(self, out, i, success): + subnlbybr(out) + if success: + self.adddiv('v'+str(i),out[6:]) + else: + self.adddiv('f'+str(i),out[19:]) + return 0 + + def feedback(self): + return self.template.render(feedback=self) + + + + def buttoncollapse(self, success, i, code ): + if success: + return Feedback.boite2(self,"debut\n"+code) + return Feedback.boite2(self,"debut False\n"+code) + + def resultat(self, code): + return '
'+code+'

' + + + + def boite2(self,html_code): + + return subnlbybr(html_code) + + + + + diff --git a/server/serverpl/filebrowser/tests/ressources/100/function001.pl b/server/serverpl/filebrowser/tests/ressources/100/function001.pl new file mode 100644 index 0000000000..31bf67bd5e --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/function001.pl @@ -0,0 +1,27 @@ +# Copyright 2016 Dominique Revuz +title=fonction +author=Dominique Revuz +name= Une fonction bob +tag=function +template=../template.pl +text== +# Fonctions + +Ecrire une fonction **bob** qui retourne la valeur entière 1238. + + >>> bob() + 1238 +== + +code== + +# Fin du code, n'écrivez pas de code après cette ligne s'il vous plait ! +# L'équipe PL +== + +pltest== +>>> bob() +1238 +>>> bob()==1238 +True +== diff --git a/server/serverpl/filebrowser/tests/ressources/100/pldoctest.py b/server/serverpl/filebrowser/tests/ressources/100/pldoctest.py new file mode 100644 index 0000000000..c7b10c2a80 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/pldoctest.py @@ -0,0 +1,2880 @@ +# Module doctest. +# Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org). +# Major enhancements and refactoring by: +# Jim Fulton +# Edward Loper +# Minor enhancements and refactoring by: +# dominique revuz +# Minor enhancements and refactoring by: +# dominique revuz +# Provided as-is; use at your own risk; no warranty; no promises; enjoy! + +r"""Module doctest -- a framework for running examples in docstrings. + +In simplest use, end each module M to be tested with: + +def _test(): + import doctest + doctest.testmod() + +if __name__ == "__main__": + _test() + +Then running the module as a script will cause the examples in the +docstrings to get executed and verified: + +python M.py + +This won't display anything unless an example fails, in which case the +failing example(s) and the cause(s) of the failure(s) are printed to stdout +(why not stderr? because stderr is a lame hack <0.2 wink>), and the final +line of output is "Test failed.". + +Run it with the -v switch instead: + +python M.py -v + +and a detailed report of all examples tried is printed to stdout, along +with assorted summaries at the end. + +You can force verbose mode by passing "verbose=True" to testmod, or prohibit +it by passing "verbose=False". In either of those cases, sys.argv is not +examined by testmod. + +There are a variety of other ways to run doctests, including integration +with the unittest framework, and support for running non-Python text +files containing doctests. There are also many ways to override parts +of doctest's default behaviors. See the Library Reference Manual for +details. +""" + +__docformat__ = 'reStructuredText en' + +__all__ = [ + # 0, Option Flags + 'register_optionflag', + 'DONT_ACCEPT_TRUE_FOR_1', + 'DONT_ACCEPT_BLANKLINE', + 'NORMALIZE_WHITESPACE', + 'ELLIPSIS', + 'SKIP', + 'IGNORE_EXCEPTION_DETAIL', + 'COMPARISON_FLAGS', + 'REPORT_UDIFF', + 'REPORT_CDIFF', + 'REPORT_NDIFF', + + 'REPORTING_FLAGS', + 'FAIL_FAST', + # 1. Utility Functions + # 2. Example & DocTest + 'Example', + 'DocTest', + # 3. Doctest Parser + 'DocTestParser', + # 4. Doctest Finder + 'DocTestFinder', + # 5. Doctest Runner + 'DocTestRunner', + 'OutputChecker', + 'DocTestFailure', + 'UnexpectedException', + 'DebugRunner', + # 6. Test Functions + 'testmod', + 'testfile', + 'run_docstring_examples', + # 7. Unittest Support + 'DocTestSuite', + 'DocFileSuite', + 'set_unittest_reportflags', + # 8. Debugging Support + 'script_from_examples', + 'testsource', + 'debug_src', + 'debug', +] + +import __future__ +import argparse +import difflib +import inspect +import linecache +import os +import pdb +import re +import sys +import traceback +import unittest +from io import StringIO +from feedback import Feedback +from plgrader import Grader +from collections import namedtuple + +TestResults = namedtuple('TestResults', 'failed attempted') + +# There are 4 basic classes: +# - Example: a pair, plus an intra-docstring line number. +# - DocTest: a collection of examples, parsed from a docstring, plus +# info about where the docstring came from (name, filename, lineno). +# - DocTestFinder: extracts DocTests from a given object's docstring and +# its contained objects' docstrings. +# - DocTestRunner: runs DocTest cases, and accumulates statistics. +# +# So the basic picture is: +# +# list of: +# +------+ +---------+ +-------+ +# |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results| +# +------+ +---------+ +-------+ +# | Example | +# | ... | +# | Example | +# +---------+ + +# Option constants. + +OPTIONFLAGS_BY_NAME = {} +def register_optionflag(name): + # Create a new flag unless `name` is already known. + return OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(OPTIONFLAGS_BY_NAME)) + +DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1') +DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE') +NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE') +ELLIPSIS = register_optionflag('ELLIPSIS') +SKIP = register_optionflag('SKIP') +IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL') + +COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 | + DONT_ACCEPT_BLANKLINE | + NORMALIZE_WHITESPACE | + ELLIPSIS | + SKIP | + IGNORE_EXCEPTION_DETAIL) + +REPORT_UDIFF = register_optionflag('REPORT_UDIFF') +REPORT_CDIFF = register_optionflag('REPORT_CDIFF') +REPORT_NDIFF = register_optionflag('REPORT_NDIFF') +FAIL_FAST = register_optionflag('FAIL_FAST') + +REPORTING_FLAGS = (REPORT_UDIFF | + REPORT_CDIFF | + REPORT_NDIFF | + + FAIL_FAST) + +# Special string markers for use in `want` strings: +BLANKLINE_MARKER = '' +ELLIPSIS_MARKER = '...' + +###################################################################### +## Table of Contents +###################################################################### +# 1. Utility Functions +# 2. Example & DocTest -- store test cases +# 3. DocTest Parser -- extracts examples from strings +# 4. DocTest Finder -- extracts test cases from objects +# 5. DocTest Runner -- runs test cases +# 6. Test Functions -- convenient wrappers for testing +# 7. Unittest Support +# 8. Debugging Support +# 9. Example Usage + +###################################################################### +## 1. Utility Functions +###################################################################### + +def _extract_future_flags(globs): + """ + Return the compiler-flags associated with the future features that + have been imported into the given namespace (globs). + """ + flags = 0 + for fname in __future__.all_feature_names: + feature = globs.get(fname, None) + if feature is getattr(__future__, fname): + flags |= feature.compiler_flag + return flags + +#~ def _normalize_module(module, depth=2): + #~ """ + #~ Return the module specified by `module`. In particular: + #~ - If `module` is a module, then return module. + #~ - If `module` is a string, then import and return the + #~ module with that name. + #~ - If `module` is None, then return the calling module. + #~ The calling module is assumed to be the module of + #~ the stack frame at the given depth in the call stack. + #~ """ + #~ if inspect.ismodule(module): + #~ return module + #~ elif isinstance(module, str): + #~ return __import__(module, globals(), locals(), ["*"]) + #~ elif module is None: + #~ return sys.modules[sys._getframe(depth).f_globals['__name__']] + #~ else: + #~ raise TypeError("Expected a module, string, or None") + +#~ def _load_testfile(filename, package, module_relative, encoding): + #~ if module_relative: + #~ package = _normalize_module(package, 3) + #~ filename = _module_relative_path(package, filename) + #~ if getattr(package, '__loader__', None) is not None: + #~ if hasattr(package.__loader__, 'get_data'): + #~ file_contents = package.__loader__.get_data(filename) + #~ file_contents = file_contents.decode(encoding) + #~ # get_data() opens files as 'rb', so one must do the equivalent + #~ # conversion as universal newlines would do. + #~ return file_contents.replace(os.linesep, '\n'), filename + #~ with open(filename, encoding=encoding) as f: + #~ return f.read(), filename + +def _indent(s, indent=4): + """ + Add the given number of space characters to the beginning of + every non-blank line in `s`, and return the result. + """ + # This regexp matches the start of non-blank lines: + return re.sub('(?m)^(?!$)', indent*' ', s) + +def _exception_traceback(exc_info): + """ + Return a string containing a traceback message for the given + exc_info tuple (as returned by sys.exc_info()). + """ + # Get a traceback message. + excout = StringIO() + exc_type, exc_val, exc_tb = exc_info + traceback.print_exception(exc_type, exc_val, exc_tb, file=excout) + return excout.getvalue() + +# Override some StringIO methods. +class _SpoofOut(StringIO): + def getvalue(self): + result = StringIO.getvalue(self) + # If anything at all was written, make sure there's a trailing + # newline. There's no way for the expected output to indicate + # that a trailing newline is missing. + if result and not result.endswith("\n"): + result += "\n" + return result + + def truncate(self, size=None): + self.seek(size) + StringIO.truncate(self) + +# Worst-case linear-time ellipsis matching. +def _ellipsis_match(want, got): + """ + Essentially the only subtle case: + >>> _ellipsis_match('aa...aa', 'aaa') + False + """ + if ELLIPSIS_MARKER not in want: + return want == got + + # Find "the real" strings. + ws = want.split(ELLIPSIS_MARKER) + assert len(ws) >= 2 + + # Deal with exact matches possibly needed at one or both ends. + startpos, endpos = 0, len(got) + w = ws[0] + if w: # starts with exact match + if got.startswith(w): + startpos = len(w) + del ws[0] + else: + return False + w = ws[-1] + if w: # ends with exact match + if got.endswith(w): + endpos -= len(w) + del ws[-1] + else: + return False + + if startpos > endpos: + # Exact end matches required more characters than we have, as in + # _ellipsis_match('aa...aa', 'aaa') + return False + + # For the rest, we only need to find the leftmost non-overlapping + # match for each piece. If there's no overall match that way alone, + # there's no overall match period. + for w in ws: + # w may be '' at times, if there are consecutive ellipses, or + # due to an ellipsis at the start or end of `want`. That's OK. + # Search for an empty string succeeds, and doesn't change startpos. + startpos = got.find(w, startpos, endpos) + if startpos < 0: + return False + startpos += len(w) + + return True + +def _comment_line(line): + "Return a commented form of the given line" + line = line.rstrip() + if line: + return '# '+line + else: + return '#' + +def _strip_exception_details(msg): + # Support for IGNORE_EXCEPTION_DETAIL. + # Get rid of everything except the exception name; in particular, drop + # the possibly dotted module path (if any) and the exception message (if + # any). We assume that a colon is never part of a dotted name, or of an + # exception name. + # E.g., given + # "foo.bar.MyError: la di da" + # return "MyError" + # Or for "abc.def" or "abc.def:\n" return "def". + + start, end = 0, len(msg) + # The exception name must appear on the first line. + i = msg.find("\n") + if i >= 0: + end = i + # retain up to the first colon (if any) + i = msg.find(':', 0, end) + if i >= 0: + end = i + # retain just the exception name + i = msg.rfind('.', 0, end) + if i >= 0: + start = i+1 + return msg[start: end] + +class _OutputRedirectingPdb(pdb.Pdb): + """ + A specialized version of the python debugger that redirects stdout + to a given stream when interacting with the user. Stdout is *not* + redirected when traced code is executed. + """ + def __init__(self, out): + self.__out = out + self.__debugger_used = False + # do not play signal games in the pdb + pdb.Pdb.__init__(self, stdout=out, nosigint=True) + # still use input() to get user input + self.use_rawinput = 1 + + def set_trace(self, frame=None): + self.__debugger_used = True + if frame is None: + frame = sys._getframe().f_back + pdb.Pdb.set_trace(self, frame) + + def set_continue(self): + # Calling set_continue unconditionally would break unit test + # coverage reporting, as Bdb.set_continue calls sys.settrace(None). + if self.__debugger_used: + pdb.Pdb.set_continue(self) + + def trace_dispatch(self, *args): + # Redirect stdout to the given stream. + save_stdout = sys.stdout + sys.stdout = self.__out + # Call Pdb's trace dispatch method. + try: + return pdb.Pdb.trace_dispatch(self, *args) + finally: + sys.stdout = save_stdout + +# [XX] Normalize with respect to os.path.pardir? +def _module_relative_path(module, path): + if not inspect.ismodule(module): + raise TypeError('Expected a module: %r' % module) + if path.startswith('/'): + raise ValueError('Module-relative files may not have absolute paths') + + # Find the base directory for the path. + if hasattr(module, '__file__'): + # A normal module/package + basedir = os.path.split(module.__file__)[0] + elif module.__name__ == '__main__': + # An interactive session. + if len(sys.argv)>0 and sys.argv[0] != '': + basedir = os.path.split(sys.argv[0])[0] + else: + basedir = os.curdir + else: + # A module w/o __file__ (this includes builtins) + raise ValueError("Can't resolve paths relative to the module " + + module + " (it has no __file__)") + + # Combine the base directory and the path. + return os.path.join(basedir, *(path.split('/'))) + +###################################################################### +## 2. Example & DocTest +###################################################################### +## - An "example" is a pair, where "source" is a +## fragment of source code, and "want" is the expected output for +## "source." The Example class also includes information about +## where the example was extracted from. +## +## - A "doctest" is a collection of examples, typically extracted from +## a string (such as an object's docstring). The DocTest class also +## includes information about where the string was extracted from. + +class Example: + """ + A single doctest example, consisting of source code and expected + output. `Example` defines the following attributes: + + - source: A single Python statement, always ending with a newline. + The constructor adds a newline if needed. + + - want: The expected output from running the source code (either + from stdout, or a traceback in case of exception). `want` ends + with a newline unless it's empty, in which case it's an empty + string. The constructor adds a newline if needed. + + - exc_msg: The exception message generated by the example, if + the example is expected to generate an exception; or `None` if + it is not expected to generate an exception. This exception + message is compared against the return value of + `traceback.format_exception_only()`. `exc_msg` ends with a + newline unless it's `None`. The constructor adds a newline + if needed. + + - lineno: The line number within the DocTest string containing + this Example where the Example begins. This line number is + zero-based, with respect to the beginning of the DocTest. + + - indent: The example's indentation in the DocTest string. + I.e., the number of space characters that precede the + example's first prompt. + + - options: A dictionary mapping from option flags to True or + False, which is used to override default options for this + example. Any option flags not contained in this dictionary + are left at their default value (as specified by the + DocTestRunner's optionflags). By default, no options are set. + """ + def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, + options=None): + # Normalize inputs. + if not source.endswith('\n'): + source += '\n' + if want and not want.endswith('\n'): + want += '\n' + if exc_msg is not None and not exc_msg.endswith('\n'): + exc_msg += '\n' + # Store properties. + self.source = source + self.want = want + self.lineno = lineno + self.indent = indent + if options is None: options = {} + self.options = options + self.exc_msg = exc_msg + + def __eq__(self, other): + if type(self) is not type(other): + return NotImplemented + + return self.source == other.source and \ + self.want == other.want and \ + self.lineno == other.lineno and \ + self.indent == other.indent and \ + self.options == other.options and \ + self.exc_msg == other.exc_msg + + def __hash__(self): + return hash((self.source, self.want, self.lineno, self.indent, + self.exc_msg)) + +class DocTest: + """ + A collection of doctest examples that should be run in a single + namespace. Each `DocTest` defines the following attributes: + + - examples: the list of examples. + + - globs: The namespace (aka globals) that the examples should + be run in. + + - name: A name identifying the DocTest (typically, the name of + the object whose docstring this DocTest was extracted from). + + - filename: The name of the file that this DocTest was extracted + from, or `None` if the filename is unknown. + + - lineno: The line number within filename where this DocTest + begins, or `None` if the line number is unavailable. This + line number is zero-based, with respect to the beginning of + the file. + + - docstring: The string that the examples were extracted from, + or `None` if the string is unavailable. + """ + def __init__(self, examples, globs, name, filename, lineno, docstring): + """ + Create a new DocTest containing the given examples. The + DocTest's globals are initialized with a copy of `globs`. + """ + assert not isinstance(examples, str), \ + "DocTest no longer accepts str; use DocTestParser instead" + self.examples = examples + self.docstring = docstring + self.globs = globs.copy() + self.name = name + self.filename = filename + self.lineno = lineno + + def __repr__(self): + if len(self.examples) == 0: + examples = 'no examples' + elif len(self.examples) == 1: + examples = '1 example' + else: + examples = '%d examples' % len(self.examples) + return ('<%s %s from %s:%s (%s)>' % + (self.__class__.__name__, + self.name, self.filename, self.lineno, examples)) + + def __eq__(self, other): + if type(self) is not type(other): + return NotImplemented + + return self.examples == other.examples and \ + self.docstring == other.docstring and \ + self.globs == other.globs and \ + self.name == other.name and \ + self.filename == other.filename and \ + self.lineno == other.lineno + + def __hash__(self): + return hash((self.docstring, self.name, self.filename, self.lineno)) + + # This lets us sort tests by name: + def __lt__(self, other): + if not isinstance(other, DocTest): + return NotImplemented + return ((self.name, self.filename, self.lineno, id(self)) + < + (other.name, other.filename, other.lineno, id(other))) + +###################################################################### +## 3. DocTestParser +###################################################################### + +class DocTestParser: + """ + A class used to parse strings containing doctest examples. + """ + # This regular expression is used to find doctest examples in a + # string. It defines three groups: `source` is the source code + # (including leading indentation and prompts); `indent` is the + # indentation of the first (PS1) line of the source code; and + # `want` is the expected output (including leading indentation). + _EXAMPLE_RE = re.compile(r''' + # Source consists of a PS1 line followed by zero or more PS2 lines. + (?P + (?:^(?P [ ]*) >>> .*) # PS1 line + (?:\n [ ]* \.\.\. .*)*) # PS2 lines + \n? + # Want consists of any non-blank lines that do not start with PS1. + (?P (?:(?![ ]*$) # Not a blank line + (?![ ]*>>>) # Not a line starting with PS1 + .+$\n? # But any other line + )*) + ''', re.MULTILINE | re.VERBOSE) + + # A regular expression for handling `want` strings that contain + # expected exceptions. It divides `want` into three pieces: + # - the traceback header line (`hdr`) + # - the traceback stack (`stack`) + # - the exception message (`msg`), as generated by + # traceback.format_exception_only() + # `msg` may have multiple lines. We assume/require that the + # exception message is the first non-indented line starting with a word + # character following the traceback header line. + _EXCEPTION_RE = re.compile(r""" + # Grab the traceback header. Different versions of Python have + # said different things on the first traceback line. + ^(?P Traceback\ \( + (?: most\ recent\ call\ last + | innermost\ last + ) \) : + ) + \s* $ # toss trailing whitespace on the header. + (?P .*?) # don't blink: absorb stuff until... + ^ (?P \w+ .*) # a line *starts* with alphanum. + """, re.VERBOSE | re.MULTILINE | re.DOTALL) + + # A callable returning a true value iff its argument is a blank line + # or contains a single comment. + _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match + + def parse(self, string, name=''): + """ + Divide the given string into examples and intervening text, + and return them as a list of alternating Examples and strings. + Line numbers for the Examples are 0-based. The optional + argument `name` is a name identifying this string, and is only + used for error messages. + """ + string = string.expandtabs() + # If all lines begin with the same indentation, then strip it. + min_indent = self._min_indent(string) + if min_indent > 0: + string = '\n'.join([l[min_indent:] for l in string.split('\n')]) + + output = [] + charno, lineno = 0, 0 + # Find all doctest examples in the string: + for m in self._EXAMPLE_RE.finditer(string): + # Add the pre-example text to `output`. + output.append(string[charno:m.start()]) + # Update lineno (lines before this example) + lineno += string.count('\n', charno, m.start()) + # Extract info from the regexp match. + (source, options, want, exc_msg) = \ + self._parse_example(m, name, lineno) + # Create an Example, and add it to the list. + if not self._IS_BLANK_OR_COMMENT(source): + output.append( Example(source, want, exc_msg, + lineno=lineno, + indent=min_indent+len(m.group('indent')), + options=options) ) + # Update lineno (lines inside this example) + lineno += string.count('\n', m.start(), m.end()) + # Update charno. + charno = m.end() + # Add any remaining post-example text to `output`. + output.append(string[charno:]) + return output + + def get_doctest(self, string, globs, name, filename, lineno): + """ + Extract all doctest examples from the given string, and + collect them into a `DocTest` object. + + `globs`, `name`, `filename`, and `lineno` are attributes for + the new `DocTest` object. See the documentation for `DocTest` + for more information. + """ + return DocTest(self.get_examples(string, name), globs, + name, filename, lineno, string) + + def get_examples(self, string, name=''): + """ + Extract all doctest examples from the given string, and return + them as a list of `Example` objects. Line numbers are + 0-based, because it's most common in doctests that nothing + interesting appears on the same line as opening triple-quote, + and so the first interesting line is called \"line 1\" then. + + The optional argument `name` is a name identifying this + string, and is only used for error messages. + """ + return [x for x in self.parse(string, name) + if isinstance(x, Example)] + + def _parse_example(self, m, name, lineno): + """ + Given a regular expression match from `_EXAMPLE_RE` (`m`), + return a pair `(source, want)`, where `source` is the matched + example's source code (with prompts and indentation stripped); + and `want` is the example's expected output (with indentation + stripped). + + `name` is the string's name, and `lineno` is the line number + where the example starts; both are used for error messages. + """ + # Get the example's indentation level. + indent = len(m.group('indent')) + + # Divide source into lines; check that they're properly + # indented; and then strip their indentation & prompts. + source_lines = m.group('source').split('\n') + self._check_prompt_blank(source_lines, indent, name, lineno) + self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno) + source = '\n'.join([sl[indent+4:] for sl in source_lines]) + + # Divide want into lines; check that it's properly indented; and + # then strip the indentation. Spaces before the last newline should + # be preserved, so plain rstrip() isn't good enough. + want = m.group('want') + want_lines = want.split('\n') + if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): + del want_lines[-1] # forget final newline & spaces after it + self._check_prefix(want_lines, ' '*indent, name, + lineno + len(source_lines)) + want = '\n'.join([wl[indent:] for wl in want_lines]) + + # If `want` contains a traceback message, then extract it. + m = self._EXCEPTION_RE.match(want) + if m: + exc_msg = m.group('msg') + else: + exc_msg = None + + # Extract options from the source. + options = self._find_options(source, name, lineno) + + return source, options, want, exc_msg + + # This regular expression looks for option directives in the + # source code of an example. Option directives are comments + # starting with "doctest:". Warning: this may give false + # positives for string-literals that contain the string + # "#doctest:". Eliminating these false positives would require + # actually parsing the string; but we limit them by ignoring any + # line containing "#doctest:" that is *followed* by a quote mark. + _OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$', + re.MULTILINE) + + def _find_options(self, source, name, lineno): + """ + Return a dictionary containing option overrides extracted from + option directives in the given source string. + + `name` is the string's name, and `lineno` is the line number + where the example starts; both are used for error messages. + """ + options = {} + # (note: with the current regexp, this will match at most once:) + for m in self._OPTION_DIRECTIVE_RE.finditer(source): + option_strings = m.group(1).replace(',', ' ').split() + for option in option_strings: + if (option[0] not in '+-' or + option[1:] not in OPTIONFLAGS_BY_NAME): + raise ValueError('line %r of the doctest for %s ' + 'has an invalid option: %r' % + (lineno+1, name, option)) + flag = OPTIONFLAGS_BY_NAME[option[1:]] + options[flag] = (option[0] == '+') + if options and self._IS_BLANK_OR_COMMENT(source): + raise ValueError('line %r of the doctest for %s has an option ' + 'directive on a line with no example: %r' % + (lineno, name, source)) + return options + + # This regular expression finds the indentation of every non-blank + # line in a string. + _INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE) + + def _min_indent(self, s): + "Return the minimum indentation of any non-blank line in `s`" + indents = [len(indent) for indent in self._INDENT_RE.findall(s)] + if len(indents) > 0: + return min(indents) + else: + return 0 + + def _check_prompt_blank(self, lines, indent, name, lineno): + """ + Given the lines of a source string (including prompts and + leading indentation), check to make sure that every prompt is + followed by a space character. If any line is not followed by + a space character, then raise ValueError. + """ + for i, line in enumerate(lines): + if len(line) >= indent+4 and line[indent+3] != ' ': + raise ValueError('line %r of the docstring for %s ' + 'lacks blank after %s: %r' % + (lineno+i+1, name, + line[indent:indent+3], line)) + + def _check_prefix(self, lines, prefix, name, lineno): + """ + Check that every line in the given list starts with the given + prefix; if any line does not, then raise a ValueError. + """ + for i, line in enumerate(lines): + if line and not line.startswith(prefix): + raise ValueError('line %r of the docstring for %s has ' + 'inconsistent leading whitespace: %r' % + (lineno+i+1, name, line)) + + +###################################################################### +## 4. DocTest Finder +###################################################################### + +class DocTestFinder: + """ + A class used to extract the DocTests that are relevant to a given + object, from its docstring and the docstrings of its contained + objects. Doctests can currently be extracted from the following + object types: modules, functions, classes, methods, staticmethods, + classmethods, and properties. + """ + + def __init__(self, verbose=False, parser=DocTestParser(), + recurse=True, exclude_empty=True): + """ + Create a new doctest finder. + + The optional argument `parser` specifies a class or + function that should be used to create new DocTest objects (or + objects that implement the same interface as DocTest). The + signature for this factory function should match the signature + of the DocTest constructor. + + If the optional argument `recurse` is false, then `find` will + only examine the given object, and not any contained objects. + + If the optional argument `exclude_empty` is false, then `find` + will include tests for objects with empty docstrings. + """ + self._parser = parser + self._verbose = verbose + self._recurse = recurse + self._exclude_empty = exclude_empty + + def find(self, obj, name=None, module=None, globs=None, extraglobs=None): + """ + Return a list of the DocTests that are defined by the given + object's docstring, or by any of its contained objects' + docstrings. + + The optional parameter `module` is the module that contains + the given object. If the module is not specified or is None, then + the test finder will attempt to automatically determine the + correct module. The object's module is used: + + - As a default namespace, if `globs` is not specified. + - To prevent the DocTestFinder from extracting DocTests + from objects that are imported from other modules. + - To find the name of the file containing the object. + - To help find the line number of the object within its + file. + + Contained objects whose module does not match `module` are ignored. + + If `module` is False, no attempt to find the module will be made. + This is obscure, of use mostly in tests: if `module` is False, or + is None but cannot be found automatically, then all objects are + considered to belong to the (non-existent) module, so all contained + objects will (recursively) be searched for doctests. + + The globals for each DocTest is formed by combining `globs` + and `extraglobs` (bindings in `extraglobs` override bindings + in `globs`). A new copy of the globals dictionary is created + for each DocTest. If `globs` is not specified, then it + defaults to the module's `__dict__`, if specified, or {} + otherwise. If `extraglobs` is not specified, then it defaults + to {}. + + """ + # If name was not specified, then extract it from the object. + if name is None: + name = getattr(obj, '__name__', None) + if name is None: + raise ValueError("DocTestFinder.find: name must be given " + "when obj.__name__ doesn't exist: %r" % + (type(obj),)) + + # Find the module that contains the given object (if obj is + # a module, then module=obj.). Note: this may fail, in which + # case module will be None. + if module is False: + module = None + elif module is None: + module = inspect.getmodule(obj) + + # Read the module's source code. This is used by + # DocTestFinder._find_lineno to find the line number for a + # given object's docstring. + try: + file = inspect.getsourcefile(obj) + except TypeError: + source_lines = None + else: + if not file: + # Check to see if it's one of our special internal "files" + # (see __patched_linecache_getlines). + file = inspect.getfile(obj) + if not file[0]+file[-2:] == '<]>': file = None + if file is None: + source_lines = None + else: + if module is not None: + # Supply the module globals in case the module was + # originally loaded via a PEP 302 loader and + # file is not a valid filesystem path + source_lines = linecache.getlines(file, module.__dict__) + else: + # No access to a loader, so assume it's a normal + # filesystem path + source_lines = linecache.getlines(file) + if not source_lines: + source_lines = None + + # Initialize globals, and merge in extraglobs. + if globs is None: + if module is None: + globs = {} + else: + globs = module.__dict__.copy() + else: + globs = globs.copy() + if extraglobs is not None: + globs.update(extraglobs) + if '__name__' not in globs: + globs['__name__'] = '__main__' # provide a default module name + + # Recursively explore `obj`, extracting DocTests. + tests = [] + self._find(tests, obj, name, module, source_lines, globs, {}) + # Sort the tests by alpha order of names, for consistency in + # verbose-mode output. This was a feature of doctest in Pythons + # <= 2.3 that got lost by accident in 2.4. It was repaired in + # 2.4.4 and 2.5. + tests.sort() + return tests + + def _from_module(self, module, object): + """ + Return true if the given object is defined in the given + module. + """ + if module is None: + return True + elif inspect.getmodule(object) is not None: + return module is inspect.getmodule(object) + elif inspect.isfunction(object): + return module.__dict__ is object.__globals__ + elif inspect.ismethoddescriptor(object): + if hasattr(object, '__objclass__'): + obj_mod = object.__objclass__.__module__ + elif hasattr(object, '__module__'): + obj_mod = object.__module__ + else: + return True # [XX] no easy way to tell otherwise + return module.__name__ == obj_mod + elif inspect.isclass(object): + return module.__name__ == object.__module__ + elif hasattr(object, '__module__'): + return module.__name__ == object.__module__ + elif isinstance(object, property): + return True # [XX] no way not be sure. + else: + raise ValueError("object must be a class or function") + + def _find(self, tests, obj, name, module, source_lines, globs, seen): + """ + Find tests for the given object and any contained objects, and + add them to `tests`. + """ + + if self._verbose: + print('Finding tests in %s' % name) + + # If we've already processed this object, then ignore it. + if id(obj) in seen: + return + seen[id(obj)] = 1 + + # Find a test for this object, and add it to the list of tests. + test = self._get_test(obj, name, module, globs, source_lines) + if test is not None: + tests.append(test) + + # Look for tests in a module's contained objects. + if inspect.ismodule(obj) and self._recurse: + for valname, val in obj.__dict__.items(): + valname = '%s.%s' % (name, valname) + # Recurse to functions & classes. + if ((inspect.isroutine(inspect.unwrap(val)) + or inspect.isclass(val)) and + self._from_module(module, val)): + self._find(tests, val, valname, module, source_lines, + globs, seen) + + # Look for tests in a module's __test__ dictionary. + if inspect.ismodule(obj) and self._recurse: + for valname, val in getattr(obj, '__test__', {}).items(): + if not isinstance(valname, str): + raise ValueError("DocTestFinder.find: __test__ keys " + "must be strings: %r" % + (type(valname),)) + if not (inspect.isroutine(val) or inspect.isclass(val) or + inspect.ismodule(val) or isinstance(val, str)): + raise ValueError("DocTestFinder.find: __test__ values " + "must be strings, functions, methods, " + "classes, or modules: %r" % + (type(val),)) + valname = '%s.__test__.%s' % (name, valname) + self._find(tests, val, valname, module, source_lines, + globs, seen) + + # Look for tests in a class's contained objects. + if inspect.isclass(obj) and self._recurse: + for valname, val in obj.__dict__.items(): + # Special handling for staticmethod/classmethod. + if isinstance(val, staticmethod): + val = getattr(obj, valname) + if isinstance(val, classmethod): + val = getattr(obj, valname).__func__ + + # Recurse to methods, properties, and nested classes. + if ((inspect.isroutine(val) or inspect.isclass(val) or + isinstance(val, property)) and + self._from_module(module, val)): + valname = '%s.%s' % (name, valname) + self._find(tests, val, valname, module, source_lines, + globs, seen) + + def _get_test(self, obj, name, module, globs, source_lines): + """ + Return a DocTest for the given object, if it defines a docstring; + otherwise, return None. + """ + # Extract the object's docstring. If it doesn't have one, + # then return None (no test for this object). + if isinstance(obj, str): + docstring = obj + else: + try: + if obj.__doc__ is None: + docstring = '' + else: + docstring = obj.__doc__ + if not isinstance(docstring, str): + docstring = str(docstring) + except (TypeError, AttributeError): + docstring = '' + + # Find the docstring's location in the file. + lineno = self._find_lineno(obj, source_lines) + + # Don't bother if the docstring is empty. + if self._exclude_empty and not docstring: + return None + + # Return a DocTest for this object. + if module is None: + filename = None + else: + filename = getattr(module, '__file__', module.__name__) + if filename[-4:] == ".pyc": + filename = filename[:-1] + return self._parser.get_doctest(docstring, globs, name, + filename, lineno) + + def _find_lineno(self, obj, source_lines): + """ + Return a line number of the given object's docstring. Note: + this method assumes that the object has a docstring. + """ + lineno = None + + # Find the line number for modules. + if inspect.ismodule(obj): + lineno = 0 + + # Find the line number for classes. + # Note: this could be fooled if a class is defined multiple + # times in a single file. + if inspect.isclass(obj): + if source_lines is None: + return None + pat = re.compile(r'^\s*class\s*%s\b' % + getattr(obj, '__name__', '-')) + for i, line in enumerate(source_lines): + if pat.match(line): + lineno = i + break + + # Find the line number for functions & methods. + if inspect.ismethod(obj): obj = obj.__func__ + if inspect.isfunction(obj): obj = obj.__code__ + if inspect.istraceback(obj): obj = obj.tb_frame + if inspect.isframe(obj): obj = obj.f_code + if inspect.iscode(obj): + lineno = getattr(obj, 'co_firstlineno', None)-1 + + # Find the line number where the docstring starts. Assume + # that it's the first line that begins with a quote mark. + # Note: this could be fooled by a multiline function + # signature, where a continuation line begins with a quote + # mark. + if lineno is not None: + if source_lines is None: + return lineno+1 + pat = re.compile('(^|.*:)\s*\w*("|\')') + for lineno in range(lineno, len(source_lines)): + if pat.match(source_lines[lineno]): + return lineno + + # We couldn't find the line number. + return None + +###################################################################### +## 5. DocTest Runner +###################################################################### + +class DocTestRunner: + """ + A class used to run DocTest test cases, and accumulate statistics. + The `run` method is used to process a single DocTest case. It + returns a tuple `(f, t)`, where `t` is the number of test cases + tried, and `f` is the number of test cases that failed. + + >>> tests = DocTestFinder().find(_TestClass) + >>> runner = DocTestRunner(verbose=False) + >>> tests.sort(key = lambda test: test.name) + >>> for test in tests: + ... print(test.name, '->', runner.run(test)) + _TestClass -> TestResults(failed=0, attempted=2) + _TestClass.__init__ -> TestResults(failed=0, attempted=2) + _TestClass.get -> TestResults(failed=0, attempted=2) + _TestClass.square -> TestResults(failed=0, attempted=1) + + The `summarize` method prints a summary of all the test cases that + have been run by the runner, and returns an aggregated `(f, t)` + tuple: + + >>> runner.summarize(verbose=1) + 4 items passed all tests: + 2 tests in _TestClass + 2 tests in _TestClass.__init__ + 2 tests in _TestClass.get + 1 tests in _TestClass.square + 7 tests in 4 items. + 7 passed and 0 failed. + Test passed. + TestResults(failed=0, attempted=7) + + The aggregated number of tried examples and failed examples is + also available via the `tries` and `failures` attributes: + + >>> runner.tries + 7 + >>> runner.failures + 0 + + The comparison between expected outputs and actual outputs is done + by an `OutputChecker`. This comparison may be customized with a + number of option flags; see the documentation for `testmod` for + more information. If the option flags are insufficient, then the + comparison may also be customized by passing a subclass of + `OutputChecker` to the constructor. + + The test runner's display output can be controlled in two ways. + First, an output function (`out) can be passed to + `TestRunner.run`; this function will be called with strings that + should be displayed. It defaults to `sys.stdout.write`. If + capturing the output is not sufficient, then the display output + can be also customized by subclassing DocTestRunner, and + overriding the methods `report_start`, `report_success`, + `report_unexpected_exception`, and `report_failure`. + """ + # This divider string is used to separate failure messages, and to + # separate sections of the summary. + DIVIDER = " "*10 + + def __init__(self, checker=None, verbose=None, optionflags=0): + """ + Create a new test runner. + + Optional keyword arg `checker` is the `OutputChecker` that + should be used to compare the expected outputs and actual + outputs of doctest examples. + + Optional keyword arg 'verbose' prints lots of stuff if true, + only failures if false; by default, it's true iff '-v' is in + sys.argv. + + Optional argument `optionflags` can be used to control how the + test runner compares expected output to actual output, and how + it displays failures. See the documentation for `testmod` for + more information. + """ + self._checker = checker or OutputChecker() + + if verbose is None: + verbose = '-v' in sys.argv + self._verbose = verbose + self.optionflags = optionflags + self.original_optionflags = optionflags + + # Keep track of the examples we've run. + self.tries = 0 + self.failures = 0 + self._name2ft = {} + self.div=[] + + # Create a fake output target for capturing doctest output. + self._fakeout = _SpoofOut() + + #///////////////////////////////////////////////////////////////// + # Reporting methods + #///////////////////////////////////////////////////////////////// + + def report_start(self, out, test, example): + """ + Report that the test runner is about to process the given + example. (Only displays a message if verbose=True) + """ + + if self._verbose: + if example.want: + out('Trying:\n' + _indent(example.source) + + 'Expecting:\n' + _indent(example.want)) + else: + out('Trying:\n' + _indent(example.source) + + 'Expecting nothing\n') + + def report_success(self, out, test, example, got,i): + """ + Report that the given example ran successfully. (Only + displays a message if verbose=True) + """ + + + out(Feedback.buttoncollapse(self,True,i,(self._failure_header(test, example) + + self._checker.output_difference(example, got, self.optionflags)))) + + if self._verbose: + + out("ok\n") + + def report_failure(self, out, test, example, got, i): + """ + Report that the given example failed. + """ + + + out(Feedback.buttoncollapse(self,False,i,(self._failure_header(test, example) + + self._checker.output_difference(example, got, self.optionflags)))) + + def report_unexpected_exception(self, out, test, example, exc_info): + """ + Report that the given example raised an unexpected exception. + """ + out(self._failure_header(test, example) + + 'Exception raised:\n' + _indent(_exception_traceback(exc_info))) + + def _failure_header(self, test, example): + out = [self.DIVIDER] + + if test.filename: + if test.lineno is not None and example.lineno is not None: + lineno = test.lineno + example.lineno + 1 + else: + lineno = '?' + #out.append('File "%s", line %s, in %s' % + # (test.filename, lineno, test.name)) + else: + out.append('Line %s, in %s' % (example.lineno+1, test.name)) + #out.append('le test qui ne fonctionne pas:') + source = example.source + + out.append(_indent(source)) + #out.append(_indent(source)) + + return '\n'.join(out) + + #///////////////////////////////////////////////////////////////// + # DocTest Running + #///////////////////////////////////////////////////////////////// + + def __run(self, test, compileflags, out): + """ + Run the examples in `test`. Write the outcome of each example + with one of the `DocTestRunner.report_*` methods, using the + writer function `out`. `compileflags` is the set of compiler + flags that should be used to execute examples. Return a tuple + `(f, t)`, where `t` is the number of examples tried, and `f` + is the number of examples that failed. The examples are run + in the namespace `test.globs`. + """ + # Keep track of the number of failures and tries. + failures = tries = 0 + + # Save the option flags (since option directives can be used + # to modify them). + original_optionflags = self.optionflags + + SUCCESS, FAILURE, BOOM = range(3) # `outcome` state + + check = self._checker.check_output + quiet=False + # Process each example. + i=0; + for examplenum, example in enumerate(test.examples): + i=i+1 + # If REPORT_ONLY_FIRST_FAILURE is set, then suppress + # reporting after the first failure. + quiet = (self.optionflags and failures > 0) + + # Merge in the example's options. + self.optionflags = original_optionflags + + if example.options: + for (optionflag, val) in example.options.items(): + if val: + self.optionflags |= optionflag + else: + self.optionflags &= ~optionflag + + # If 'SKIP' is set, then skip this example. + if self.optionflags & SKIP: + raise Exception("zzz") + continue + + # Record that we started this example. + tries += 1 + if not quiet: + self.report_start(out, test, example) + + # Use a special filename for compile(), so we can retrieve + # the source code during interactive debugging (see + # __patched_linecache_getlines). + filename = '' % (test.name, examplenum) + + # Run the example in the given context (globs), and record + # any exception that gets raised. (But don't intercept + # keyboard interrupts.) + + try: + # Don't blink! This is where the user's code gets run. + exec(compile(example.source, filename, "single", + compileflags, 1), test.globs) + self.debugger.set_continue() # ==== Example Finished ==== + exception = None + except KeyboardInterrupt: + raise + except: + exception = sys.exc_info() + self.debugger.set_continue() # ==== Example Finished ==== + + got = self._fakeout.getvalue() # the actual output + self._fakeout.truncate(0) + outcome = FAILURE # guilty until proved innocent or insane + + # If the example executed without raising any exceptions, + # verify its output. + if exception is None: + if check(example.want, got, self.optionflags): + outcome = SUCCESS + + # The example raised an exception: check if it was expected. + else: + exc_msg = traceback.format_exception_only(*exception[:2])[-1] + if not quiet: + got += _exception_traceback(exception) + + # If `example.exc_msg` is None, then we weren't expecting + # an exception. + if example.exc_msg is None: + outcome = BOOM + + # We expected an exception: see whether it matches. + elif check(example.exc_msg, exc_msg, self.optionflags): + outcome = SUCCESS + + # Another chance if they didn't care about the detail. + elif self.optionflags & IGNORE_EXCEPTION_DETAIL: + if check(_strip_exception_details(example.exc_msg), + _strip_exception_details(exc_msg), + self.optionflags): + outcome = SUCCESS + + # Report the outcome. + if outcome is SUCCESS: + if not quiet: + self.report_success(out, test, example, got,i) + + if quiet and failures <1: + self.report_success(out, test, example, got,i) + + elif outcome is FAILURE: + if not quiet: + + self.report_failure(out, test, example, got,i) + + elif quiet and failures < 1: + + self.report_failure(out, test, example, got,i) + failures += 1 + elif outcome is BOOM: + if not quiet: + self.report_unexpected_exception(out, test, example, + exception) + + failures += 1 + else: + assert False, ("unknown outcome", outcome) + + + + # Restore the option flags (in case they were modified) + self.optionflags = original_optionflags + + # Record and return the number of failures and tries. + self.__record_outcome(test, failures, tries) + return TestResults(failures, tries) + + def __record_outcome(self, test, f, t): + """ + Record the fact that the given DocTest (`test`) generated `f` + failures out of `t` tried examples. + """ + f2, t2 = self._name2ft.get(test.name, (0,0)) + self._name2ft[test.name] = (f+f2, t+t2) + self.failures += f + self.tries += t + + __LINECACHE_FILENAME_RE = re.compile(r'.+)' + r'\[(?P\d+)\]>$') + def __patched_linecache_getlines(self, filename, module_globals=None): + m = self.__LINECACHE_FILENAME_RE.match(filename) + if m and m.group('name') == self.test.name: + example = self.test.examples[int(m.group('examplenum'))] + return example.source.splitlines(keepends=True) + else: + return self.save_linecache_getlines(filename, module_globals) + + def run(self, test, compileflags=None, out=None, clear_globs=True): + """ + Run the examples in `test`, and display the results using the + writer function `out`. + + The examples are run in the namespace `test.globs`. If + `clear_globs` is true (the default), then this namespace will + be cleared after the test runs, to help with garbage + collection. If you would like to examine the namespace after + the test completes, then use `clear_globs=False`. + + `compileflags` gives the set of flags that should be used by + the Python compiler when running the examples. If not + specified, then it will default to the set of future-import + flags that apply to `globs`. + + The output of each example is checked using + `DocTestRunner.check_output`, and the results are formatted by + the `DocTestRunner.report_*` methods. + """ + self.test = test + + if compileflags is None: + compileflags = _extract_future_flags(test.globs) + + save_stdout = sys.stdout + if out is None: + encoding = save_stdout.encoding + if encoding is None or encoding.lower() == 'utf-8': + out = save_stdout.write + else: + # Use backslashreplace error handling on write + def out(s): + s = str(s.encode(encoding, 'backslashreplace'), encoding) + save_stdout.write(s) + sys.stdout = self._fakeout + + # Patch pdb.set_trace to restore sys.stdout during interactive + # debugging (so it's not still redirected to self._fakeout). + # Note that the interactive output will go to *our* + # save_stdout, even if that's not the real sys.stdout; this + # allows us to write test cases for the set_trace behavior. + save_trace = sys.gettrace() + save_set_trace = pdb.set_trace + self.debugger = _OutputRedirectingPdb(save_stdout) + self.debugger.reset() + pdb.set_trace = self.debugger.set_trace + + # Patch linecache.getlines, so we can see the example's source + # when we're inside the debugger. + self.save_linecache_getlines = linecache.getlines + linecache.getlines = self.__patched_linecache_getlines + + # Make sure sys.displayhook just prints the value to stdout + save_displayhook = sys.displayhook + sys.displayhook = sys.__displayhook__ + + try: + return self.__run(test, compileflags, out) + finally: + sys.stdout = save_stdout + pdb.set_trace = save_set_trace + sys.settrace(save_trace) + linecache.getlines = self.save_linecache_getlines + sys.displayhook = save_displayhook + if clear_globs: + test.globs.clear() + import builtins + builtins._ = None + + #///////////////////////////////////////////////////////////////// + # Summarization + #///////////////////////////////////////////////////////////////// + def summarize(self, verbose=True): + """ + Print a summary of all the test cases that have been run by + this DocTestRunner, and return a tuple `(f, t)`, where `f` is + the total number of failed examples, and `t` is the total + number of tried examples. + + The optional `verbose` argument controls how detailed the + summary is. If the verbosity is not specified, then the + DocTestRunner's verbosity is used. + """ + + if verbose is None: + verbose = self._verbose + notests = [] + passed = [] + failed = [] + totalt = totalf = 0 + for x in self._name2ft.items(): + name, (f, t) = x + assert f <= t + totalt += t + totalf += f + if t == 0: + notests.append(name) + elif f == 0: + passed.append( (name, t) ) + else: + failed.append(x) + #if verbose: + ##if notests: + ##print(len(notests), "items had no tests:") + ##notests.sort() + ##for thing in notests: + ##print(" ", thing) + #if passed: + #print(len(passed), "items passed all tests:") + #passed.sort() + #for thing, count in passed: + #print(" %3d tests in %s" % (count, thing)) + print("debut") + print("Test:") + if failed: + + print(self.DIVIDER) + #print(len(failed), " jeu de tests avec des problèmes :") + failed.sort() + + + for thing, (f, t) in failed: + #print(" %3d tests sur %3d dans %s" % (f, t, thing)) + + print("réussi(s):") + print(totalt - totalf, "\n ratés: ",totalf,"
") + if verbose: + #print(totalt, "tests sur ", len(self._name2ft), " tests.") + print(totalt - totalf, "réussi(s) and ", totalf, "ratés sur ", totalt, "tests
") + if totalf: + + print("***Tests échoués***") + elif verbose: + print("***Tests validés***") + return TestResults(totalf, totalt) + + #///////////////////////////////////////////////////////////////// + # Backward compatibility cruft to maintain doctest.master. + #///////////////////////////////////////////////////////////////// + def merge(self, other): + d = self._name2ft + for name, (f, t) in other._name2ft.items(): + if name in d: + # Don't print here by default, since doing + # so breaks some of the buildbots + #print("*** DocTestRunner.merge: '" + name + "' in both" \ + # " testers; summing outcomes.") + f2, t2 = d[name] + f = f + f2 + t = t + t2 + d[name] = f, t + +class OutputChecker: + """ + A class used to check the whether the actual output from a doctest + example matches the expected output. `OutputChecker` defines two + methods: `check_output`, which compares a given pair of outputs, + and returns true if they match; and `output_difference`, which + returns a string describing the differences between two outputs. + """ + def _toAscii(self, s): + """ + Convert string to hex-escaped ASCII string. + """ + return str(s.encode('ASCII', 'backslashreplace'), "ASCII") + + def check_output(self, want, got, optionflags): + """ + Return True iff the actual output from an example (`got`) + matches the expected output (`want`). These strings are + always considered to match if they are identical; but + depending on what option flags the test runner is using, + several non-exact match types are also possible. See the + documentation for `TestRunner` for more information about + option flags. + """ + + # If `want` contains hex-escaped character such as "\u1234", + # then `want` is a string of six characters(e.g. [\,u,1,2,3,4]). + # On the other hand, `got` could be another sequence of + # characters such as [\u1234], so `want` and `got` should + # be folded to hex-escaped ASCII string to compare. + got = self._toAscii(got) + want = self._toAscii(want) + + # Handle the common case first, for efficiency: + # if they're string-identical, always return true. + if got == want: + return True + + # The values True and False replaced 1 and 0 as the return + # value for boolean comparisons in Python 2.3. + if not (optionflags & DONT_ACCEPT_TRUE_FOR_1): + if (got,want) == ("True\n", "1\n"): + return True + if (got,want) == ("False\n", "0\n"): + return True + + # can be used as a special sequence to signify a + # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used. + if not (optionflags & DONT_ACCEPT_BLANKLINE): + # Replace in want with a blank line. + want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER), + '', want) + # If a line in got contains only spaces, then remove the + # spaces. + got = re.sub('(?m)^\s*?$', '', got) + if got == want: + return True + + # This flag causes doctest to ignore any differences in the + # contents of whitespace strings. Note that this can be used + # in conjunction with the ELLIPSIS flag. + if optionflags & NORMALIZE_WHITESPACE: + got = ' '.join(got.split()) + want = ' '.join(want.split()) + if got == want: + return True + + # The ELLIPSIS flag says to let the sequence "..." in `want` + # match any substring in `got`. + if optionflags & ELLIPSIS: + if _ellipsis_match(want, got): + return True + + # We didn't find any match; return false. + return False + + # Should we do a fancy diff? + def _do_a_fancy_diff(self, want, got, optionflags): + # Not unless they asked for a fancy diff. + if not optionflags & (REPORT_UDIFF | + REPORT_CDIFF | + REPORT_NDIFF): + return False + + # If expected output uses ellipsis, a meaningful fancy diff is + # too hard ... or maybe not. In two real-life failures Tim saw, + # a diff was a major help anyway, so this is commented out. + # [todo] _ellipsis_match() knows which pieces do and don't match, + # and could be the basis for a kick-ass diff in this case. + ##if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want: + ## return False + + # ndiff does intraline difference marking, so can be useful even + # for 1-line differences. + if optionflags & REPORT_NDIFF: + return True + + # The other diff types need at least a few lines to be helpful. + return want.count('\n') > 2 and got.count('\n') > 2 + + def output_difference(self, example, got, optionflags): + """ + Return a string describing the differences between the + expected output for a given example (`example`) and the actual + output (`got`). `optionflags` is the set of option flags used + to compare `want` and `got`. + """ + want = example.want + # If s are being used, then replace blank lines + # with in the actual output string. + if not (optionflags & DONT_ACCEPT_BLANKLINE): + got = re.sub('(?m)^[ ]*(?=\n)', BLANKLINE_MARKER, got) + + # Check if we should use diff. + if self._do_a_fancy_diff(want, got, optionflags): + # Split want & got into lines. + want_lines = want.splitlines(keepends=True) + got_lines = got.splitlines(keepends=True) + # Use difflib to find their differences. + if optionflags & REPORT_UDIFF: + diff = difflib.unified_diff(want_lines, got_lines, n=2) + diff = list(diff)[2:] # strip the diff header + kind = 'unified diff with -expected +actual' + elif optionflags & REPORT_CDIFF: + diff = difflib.context_diff(want_lines, got_lines, n=2) + diff = list(diff)[2:] # strip the diff header + kind = 'context diff with expected followed by actual' + elif optionflags & REPORT_NDIFF: + engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) + diff = list(engine.compare(want_lines, got_lines)) + kind = 'ndiff with -expected +actual' + else: + assert 0, 'Bad diff option' + # Remove trailing whitespace on diff output. + diff = [line.rstrip() + '\n' for line in diff] + return 'Differences (%s):\n' % kind + _indent(''.join(diff)) + + # If we're not using diff, then simply list the expected + # output followed by the actual output. + if want and got: + return 'Attendu: '+Feedback.resultat(self,_indent(want))+ ' Obtenu: '+Feedback.resultat(self,_indent(got)) + 'debut' + elif want: + return 'Attendu:%s rien obtenu!\n' % _indent(want) + elif got: + return 'Attendu rien ai obtenu:\n%s' % _indent(got) + else: + return 'Attendu rien\n obtenu rien\n' + +class DocTestFailure(Exception): + """A DocTest example has failed in debugging mode. + + The exception instance has variables: + + - test: the DocTest object being run + + - example: the Example object that failed + + - got: the actual output + """ + def __init__(self, test, example, got): + self.test = test + self.example = example + self.got = got + + def __str__(self): + return str(self.test) + +class UnexpectedException(Exception): + """A DocTest example has encountered an unexpected exception + + The exception instance has variables: + + - test: the DocTest object being run + + - example: the Example object that failed + + - exc_info: the exception info + """ + def __init__(self, test, example, exc_info): + self.test = test + self.example = example + self.exc_info = exc_info + + def __str__(self): + return str(self.test) + +class DebugRunner(DocTestRunner): + r"""Run doc tests but raise an exception as soon as there is a failure. + + If an unexpected exception occurs, an UnexpectedException is raised. + It contains the test, the example, and the original exception: + + >>> runner = DebugRunner(verbose=False) + >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42', + ... {}, 'foo', 'foo.py', 0) + >>> try: + ... runner.run(test) + ... except UnexpectedException as f: + ... failure = f + + >>> failure.test is test + True + + >>> failure.example.want + '42\n' + + >>> exc_info = failure.exc_info + >>> raise exc_info[1] # Already has the traceback + Traceback (most recent call last): + ... + KeyError + + We wrap the original exception to give the calling application + access to the test and example information. + + If the output doesn't match, then a DocTestFailure is raised: + + >>> test = DocTestParser().get_doctest(''' + ... >>> x = 1 + ... >>> x + ... 2 + ... ''', {}, 'foo', 'foo.py', 0) + + >>> try: + ... runner.run(test) + ... except DocTestFailure as f: + ... failure = f + + DocTestFailure objects provide access to the test: + + >>> failure.test is test + True + + As well as to the example: + + >>> failure.example.want + '2\n' + + and the actual output: + + >>> failure.got + '1\n' + + If a failure or error occurs, the globals are left intact: + + >>> del test.globs['__builtins__'] + >>> test.globs + {'x': 1} + + >>> test = DocTestParser().get_doctest(''' + ... >>> x = 2 + ... >>> raise KeyError + ... ''', {}, 'foo', 'foo.py', 0) + + >>> runner.run(test) + Traceback (most recent call last): + ... + doctest.UnexpectedException: + + >>> del test.globs['__builtins__'] + >>> test.globs + {'x': 2} + + But the globals are cleared if there is no error: + + >>> test = DocTestParser().get_doctest(''' + ... >>> x = 2 + ... ''', {}, 'foo', 'foo.py', 0) + + >>> runner.run(test) + TestResults(failed=0, attempted=1) + + >>> test.globs + {} + + """ + + def run(self, test, compileflags=None, out=None, clear_globs=True): + r = DocTestRunner.run(self, test, compileflags, out, False) + if clear_globs: + test.globs.clear() + return r + + def report_unexpected_exception(self, out, test, example, exc_info): + raise UnexpectedException(test, example, exc_info) + + def report_failure(self, out, test, example, got): + raise DocTestFailure(test, example, got) + +###################################################################### +## 6. Test Functions +###################################################################### +# These should be backwards compatible. + +# For backward compatibility, a global instance of a DocTestRunner +# class, updated by testmod. +master = None + +def testmod(m=None, name=None, globs=None, verbose=None, + report=True, optionflags=0, extraglobs=None, + raise_on_error=False, exclude_empty=False): + """m=None, name=None, globs=None, verbose=None, report=True, + optionflags=0, extraglobs=None, raise_on_error=False, + exclude_empty=False + + Test examples in docstrings in functions and classes reachable + from module m (or the current module if m is not supplied), starting + with m.__doc__. + + Also test examples reachable from dict m.__test__ if it exists and is + not None. m.__test__ maps names to functions, classes and strings; + function and class docstrings are tested even if the name is private; + strings are tested directly, as if they were docstrings. + + Return (#failures, #tests). + + See help(doctest) for an overview. + + Optional keyword arg "name" gives the name of the module; by default + use m.__name__. + + Optional keyword arg "globs" gives a dict to be used as the globals + when executing examples; by default, use m.__dict__. A copy of this + dict is actually used for each docstring, so that each docstring's + examples start with a clean slate. + + Optional keyword arg "extraglobs" gives a dictionary that should be + merged into the globals that are used to execute examples. By + default, no extra globals are used. This is new in 2.4. + + Optional keyword arg "verbose" prints lots of stuff if true, prints + only failures if false; by default, it's true iff "-v" is in sys.argv. + + Optional keyword arg "report" prints a summary at the end when true, + else prints nothing at the end. In verbose mode, the summary is + detailed, else very brief (in fact, empty if all tests passed). + + Optional keyword arg "optionflags" or's together module constants, + and defaults to 0. This is new in 2.3. Possible values (see the + docs for details): + + DONT_ACCEPT_TRUE_FOR_1 + DONT_ACCEPT_BLANKLINE + NORMALIZE_WHITESPACE + ELLIPSIS + SKIP + IGNORE_EXCEPTION_DETAIL + REPORT_UDIFF + REPORT_CDIFF + REPORT_NDIFF + REPORT_ONLY_FIRST_FAILURE + + Optional keyword arg "raise_on_error" raises an exception on the + first unexpected exception or failure. This allows failures to be + post-mortem debugged. + + Advanced tomfoolery: testmod runs methods of a local instance of + class doctest.Tester, then merges the results into (or creates) + global Tester instance doctest.master. Methods of doctest.master + can be called directly too, if you want to do something unusual. + Passing report=0 to testmod is especially useful then, to delay + displaying a summary. Invoke doctest.master.summarize(verbose) + when you're done fiddling. + """ + global master + + # If no module was given, then use __main__. + if m is None: + # DWA - m will still be None if this wasn't invoked from the command + # line, in which case the following TypeError is about as good an error + # as we should expect + m = sys.modules.get('__main__') + + # Check that we were actually given a module. + if not inspect.ismodule(m): + raise TypeError("testmod: module required; %r" % (m,)) + + # If no name was given, then use the module's name. + if name is None: + name = m.__name__ + + # Find, parse, and run all tests in the given module. + finder = DocTestFinder(exclude_empty=exclude_empty) + + if raise_on_error: + runner = DebugRunner(verbose=verbose, optionflags=optionflags) + else: + runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + + for test in finder.find(m, name, globs=globs, extraglobs=extraglobs): + + runner.run(test) + + if report: + runner.summarize() + + if master is None: + master = runner + else: + master.merge(runner) + + return TestResults(runner.failures, runner.tries) + + +#~ def pltestfile(teststring, module_relative=True, name="Your Code", package=None, + #~ globs=None, verbose=None, report=True, optionflags=0, + #~ extraglobs=None, raise_on_error=False, parser=DocTestParser(), + #~ encoding=None): + #~ """ + #~ This is the same function than doctest.testfile but the text comme from the teststring. + + #~ """ + #~ global master + + #~ if package and not module_relative: + #~ raise ValueError("Package may only be specified for module-" + #~ "relative paths.") + + #~ # Relativize the path + #~ # forget it get the test from the string + #~ text = teststring + #~ #text, filename = _load_testfile(filename, package, module_relative, + #~ # encoding or "utf-8") + + #~ # If no name was given, then use the file's name. + #~ # ther should be a name ! see default argument + #~ #if name is None: + #~ # name = os.path.basename(filename) + #~ filename=name + + #~ # Assemble the globals. + #~ if globs is None: + #~ globs = {} + #~ else: + #~ globs = globs.copy() + #~ if extraglobs is not None: + #~ globs.update(extraglobs) + #~ if '__name__' not in globs: + #~ globs['__name__'] = '__main__' + + #~ if raise_on_error: + #~ runner = DebugRunner(verbose=verbose, optionflags=optionflags) + #~ else: + #~ runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + + #~ # Read the file, convert it to a test, and run it. + #~ test = parser.get_doctest(text, globs, name, filename, 0) + #~ runner.run(test) + + #~ if report: + #~ runner.summarize() + + #~ if master is None: + #~ master = runner + #~ else: + #~ master.merge(runner) + + #~ return TestResults(runner.failures, runner.tries) + + + + +def testfile(filename, module_relative=True, name=None, package=None, + globs=None, verbose=None, report=True, optionflags=0, + extraglobs=None, raise_on_error=False, parser=DocTestParser(), + encoding=None): + """ + Test examples in the given file. Return (#failures, #tests). + + Optional keyword arg "module_relative" specifies how filenames + should be interpreted: + + - If "module_relative" is True (the default), then "filename" + specifies a module-relative path. By default, this path is + relative to the calling module's directory; but if the + "package" argument is specified, then it is relative to that + package. To ensure os-independence, "filename" should use + "/" characters to separate path segments, and should not + be an absolute path (i.e., it may not begin with "/"). + + - If "module_relative" is False, then "filename" specifies an + os-specific path. The path may be absolute or relative (to + the current working directory). + + Optional keyword arg "name" gives the name of the test; by default + use the file's basename. + + Optional keyword argument "package" is a Python package or the + name of a Python package whose directory should be used as the + base directory for a module relative filename. If no package is + specified, then the calling module's directory is used as the base + directory for module relative filenames. It is an error to + specify "package" if "module_relative" is False. + + Optional keyword arg "globs" gives a dict to be used as the globals + when executing examples; by default, use {}. A copy of this dict + is actually used for each docstring, so that each docstring's + examples start with a clean slate. + + Optional keyword arg "extraglobs" gives a dictionary that should be + merged into the globals that are used to execute examples. By + default, no extra globals are used. + + Optional keyword arg "verbose" prints lots of stuff if true, prints + only failures if false; by default, it's true iff "-v" is in sys.argv. + + Optional keyword arg "report" prints a summary at the end when true, + else prints nothing at the end. In verbose mode, the summary is + detailed, else very brief (in fact, empty if all tests passed). + + Optional keyword arg "optionflags" or's together module constants, + and defaults to 0. Possible values (see the docs for details): + + DONT_ACCEPT_TRUE_FOR_1 + DONT_ACCEPT_BLANKLINE + NORMALIZE_WHITESPACE + ELLIPSIS + SKIP + IGNORE_EXCEPTION_DETAIL + REPORT_UDIFF + REPORT_CDIFF + REPORT_NDIFF + REPORT_ONLY_FIRST_FAILURE + + Optional keyword arg "raise_on_error" raises an exception on the + first unexpected exception or failure. This allows failures to be + post-mortem debugged. + + Optional keyword arg "parser" specifies a DocTestParser (or + subclass) that should be used to extract tests from the files. + + Optional keyword arg "encoding" specifies an encoding that should + be used to convert the file to unicode. + + Advanced tomfoolery: testmod runs methods of a local instance of + class doctest.Tester, then merges the results into (or creates) + global Tester instance doctest.master. Methods of doctest.master + can be called directly too, if you want to do something unusual. + Passing report=0 to testmod is especially useful then, to delay + displaying a summary. Invoke doctest.master.summarize(verbose) + when you're done fiddling. + """ + global master + + if package and not module_relative: + raise ValueError("Package may only be specified for module-" + "relative paths.") + + # Relativize the path + text, filename = _load_testfile(filename, package, module_relative, + encoding or "utf-8") + + # If no name was given, then use the file's name. + if name is None: + name = os.path.basename(filename) + + # Assemble the globals. + if globs is None: + globs = {} + else: + globs = globs.copy() + if extraglobs is not None: + globs.update(extraglobs) + if '__name__' not in globs: + globs['__name__'] = '__main__' + + if raise_on_error: + runner = DebugRunner(verbose=verbose, optionflags=optionflags) + else: + runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + + # Read the file, convert it to a test, and run it. + test = parser.get_doctest(text, globs, name, filename, 0) + runner.run(test) + + if report: + runner.summarize() + + if master is None: + master = runner + else: + master.merge(runner) + + return TestResults(runner.failures, runner.tries) + +def run_docstring_examples(f, globs, verbose=False, name="NoName", + compileflags=None, optionflags=0): + """ + Test examples in the given object's docstring (`f`), using `globs` + as globals. Optional argument `name` is used in failure messages. + If the optional argument `verbose` is true, then generate output + even if there are no failures. + + `compileflags` gives the set of flags that should be used by the + Python compiler when running the examples. If not specified, then + it will default to the set of future-import flags that apply to + `globs`. + + Optional keyword arg `optionflags` specifies options for the + testing and output. See the documentation for `testmod` for more + information. + """ + # Find, parse, and run all tests in the given module. + finder = DocTestFinder(verbose=verbose, recurse=False) + runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + for test in finder.find(f, name, globs=globs): + runner.run(test, compileflags=compileflags) + +###################################################################### +## 7. Unittest Support +###################################################################### + +_unittest_reportflags = 0 + +def set_unittest_reportflags(flags): + """Sets the unittest option flags. + + The old flag is returned so that a runner could restore the old + value if it wished to: + + >>> import doctest + >>> old = doctest._unittest_reportflags + >>> doctest.set_unittest_reportflags(REPORT_NDIFF | + ... REPORT_ONLY_FIRST_FAILURE) == old + True + + >>> doctest._unittest_reportflags == (REPORT_NDIFF | + ... REPORT_ONLY_FIRST_FAILURE) + True + + Only reporting flags can be set: + + >>> doctest.set_unittest_reportflags(ELLIPSIS) + Traceback (most recent call last): + ... + ValueError: ('Only reporting flags allowed', 8) + + >>> doctest.set_unittest_reportflags(old) == (REPORT_NDIFF | + ... REPORT_ONLY_FIRST_FAILURE) + True + """ + global _unittest_reportflags + + if (flags & REPORTING_FLAGS) != flags: + raise ValueError("Only reporting flags allowed", flags) + old = _unittest_reportflags + _unittest_reportflags = flags + return old + + +class DocTestCase(unittest.TestCase): + + def __init__(self, test, optionflags=0, setUp=None, tearDown=None, + checker=None): + + unittest.TestCase.__init__(self) + self._dt_optionflags = optionflags + self._dt_checker = checker + self._dt_test = test + self._dt_setUp = setUp + self._dt_tearDown = tearDown + + def setUp(self): + test = self._dt_test + + if self._dt_setUp is not None: + self._dt_setUp(test) + + def tearDown(self): + test = self._dt_test + + if self._dt_tearDown is not None: + self._dt_tearDown(test) + + test.globs.clear() + + #~ def runTest(self): + #~ test = self._dt_test + #~ old = sys.stdout + #~ new = StringIO() + #~ optionflags = self._dt_optionflags + + #~ if not (optionflags & REPORTING_FLAGS): + #~ # The option flags don't include any reporting flags, + #~ # so add the default reporting flags + #~ optionflags |= _unittest_reportflags + + #~ runner = DocTestRunner(optionflags=optionflags, + #~ checker=self._dt_checker, verbose=False) + + #~ try: + #~ runner.DIVIDER = "-"*70 + #~ failures, tries = runner.run( + #~ test, out=new.write, clear_globs=False) + #~ finally: + #~ sys.stdout = old + + #~ if failures: + #~ raise self.failureException(self.format_failure(new.getvalue())) + + #~ def format_failure(self, err): + #~ test = self._dt_test + #~ if test.lineno is None: + #~ lineno = 'unknown line number' + #~ else: + #~ lineno = '%s' % test.lineno + #~ lname = '.'.join(test.name.split('.')[-1:]) + #~ return ('Failed doctest test for %s\n' + #~ ' File "%s", line %s, in %s\n\n%s' + #~ % (test.name, test.filename, lineno, lname, err) + #~ ) + + def debug(self): + r"""Run the test case without results and without catching exceptions + + The unit test framework includes a debug method on test cases + and test suites to support post-mortem debugging. The test code + is run in such a way that errors are not caught. This way a + caller can catch the errors and initiate post-mortem debugging. + + The DocTestCase provides a debug method that raises + UnexpectedException errors if there is an unexpected + exception: + + >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42', + ... {}, 'foo', 'foo.py', 0) + >>> case = DocTestCase(test) + >>> try: + ... case.debug() + ... except UnexpectedException as f: + ... failure = f + + The UnexpectedException contains the test, the example, and + the original exception: + + >>> failure.test is test + True + + >>> failure.example.want + '42\n' + + >>> exc_info = failure.exc_info + >>> raise exc_info[1] # Already has the traceback + Traceback (most recent call last): + ... + KeyError + + If the output doesn't match, then a DocTestFailure is raised: + + >>> test = DocTestParser().get_doctest(''' + ... >>> x = 1 + ... >>> x + ... 2 + ... ''', {}, 'foo', 'foo.py', 0) + >>> case = DocTestCase(test) + + >>> try: + ... case.debug() + ... except DocTestFailure as f: + ... failure = f + + DocTestFailure objects provide access to the test: + + >>> failure.test is test + True + + As well as to the example: + + >>> failure.example.want + '2\n' + + and the actual output: + + >>> failure.got + '1\n' + + """ + + self.setUp() + runner = DebugRunner(optionflags=self._dt_optionflags, + checker=self._dt_checker, verbose=False) + runner.run(self._dt_test, clear_globs=False) + self.tearDown() + + def id(self): + return self._dt_test.name + + def __eq__(self, other): + if type(self) is not type(other): + return NotImplemented + + return self._dt_test == other._dt_test and \ + self._dt_optionflags == other._dt_optionflags and \ + self._dt_setUp == other._dt_setUp and \ + self._dt_tearDown == other._dt_tearDown and \ + self._dt_checker == other._dt_checker + + def __hash__(self): + return hash((self._dt_optionflags, self._dt_setUp, self._dt_tearDown, + self._dt_checker)) + + def __repr__(self): + name = self._dt_test.name.split('.') + return "%s (%s)" % (name[-1], '.'.join(name[:-1])) + + __str__ = __repr__ + + def shortDescription(self): + return "Doctest: " + self._dt_test.name + +class SkipDocTestCase(DocTestCase): + def __init__(self, module): + self.module = module + DocTestCase.__init__(self, None) + + def setUp(self): + self.skipTest("DocTestSuite will not work with -O2 and above") + + def test_skip(self): + pass + + def shortDescription(self): + return "Skipping tests from %s" % self.module.__name__ + + __str__ = shortDescription + + +class _DocTestSuite(unittest.TestSuite): + + def _removeTestAtIndex(self, index): + pass + + +def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, + **options): + """ + Convert doctest tests for a module to a unittest test suite. + + This converts each documentation string in a module that + contains doctest tests to a unittest test case. If any of the + tests in a doc string fail, then the test case fails. An exception + is raised showing the name of the file containing the test and a + (sometimes approximate) line number. + + The `module` argument provides the module to be tested. The argument + can be either a module or a module name. + + If no argument is given, the calling module is used. + + A number of options may be provided as keyword arguments: + + setUp + A set-up function. This is called before running the + tests in each file. The setUp function will be passed a DocTest + object. The setUp function can access the test globals as the + globs attribute of the test passed. + + tearDown + A tear-down function. This is called after running the + tests in each file. The tearDown function will be passed a DocTest + object. The tearDown function can access the test globals as the + globs attribute of the test passed. + + globs + A dictionary containing initial global variables for the tests. + + optionflags + A set of doctest option flags expressed as an integer. + """ + + if test_finder is None: + test_finder = DocTestFinder() + + module = _normalize_module(module) + tests = test_finder.find(module, globs=globs, extraglobs=extraglobs) + + if not tests and sys.flags.optimize >=2: + # Skip doctests when running with -O2 + suite = _DocTestSuite() + suite.addTest(SkipDocTestCase(module)) + return suite + + tests.sort() + suite = _DocTestSuite() + + for test in tests: + + if len(test.examples) == 0: + continue + if not test.filename: + filename = module.__file__ + if filename[-4:] == ".pyc": + filename = filename[:-1] + test.filename = filename + suite.addTest(DocTestCase(test, **options)) + + return suite + +class DocFileCase(DocTestCase): + + def id(self): + return '_'.join(self._dt_test.name.split('.')) + + def __repr__(self): + return self._dt_test.filename + __str__ = __repr__ + + def format_failure(self, err): + return ('Failed doctest test for %s\n File "%s", line 0\n\n%s' + % (self._dt_test.name, self._dt_test.filename, err) + ) + +def DocFileTest(path, module_relative=True, package=None, + globs=None, parser=DocTestParser(), + encoding=None, **options): + if globs is None: + globs = {} + else: + globs = globs.copy() + + if package and not module_relative: + raise ValueError("Package may only be specified for module-" + "relative paths.") + + # Relativize the path. + doc, path = _load_testfile(path, package, module_relative, + encoding or "utf-8") + + if "__file__" not in globs: + globs["__file__"] = path + + # Find the file and read it. + name = os.path.basename(path) + + # Convert it to a test, and wrap it in a DocFileCase. + test = parser.get_doctest(doc, globs, name, path, 0) + return DocFileCase(test, **options) + +def DocFileSuite(*paths, **kw): + """A unittest suite for one or more doctest files. + + The path to each doctest file is given as a string; the + interpretation of that string depends on the keyword argument + "module_relative". + + A number of options may be provided as keyword arguments: + + module_relative + If "module_relative" is True, then the given file paths are + interpreted as os-independent module-relative paths. By + default, these paths are relative to the calling module's + directory; but if the "package" argument is specified, then + they are relative to that package. To ensure os-independence, + "filename" should use "/" characters to separate path + segments, and may not be an absolute path (i.e., it may not + begin with "/"). + + If "module_relative" is False, then the given file paths are + interpreted as os-specific paths. These paths may be absolute + or relative (to the current working directory). + + package + A Python package or the name of a Python package whose directory + should be used as the base directory for module relative paths. + If "package" is not specified, then the calling module's + directory is used as the base directory for module relative + filenames. It is an error to specify "package" if + "module_relative" is False. + + setUp + A set-up function. This is called before running the + tests in each file. The setUp function will be passed a DocTest + object. The setUp function can access the test globals as the + globs attribute of the test passed. + + tearDown + A tear-down function. This is called after running the + tests in each file. The tearDown function will be passed a DocTest + object. The tearDown function can access the test globals as the + globs attribute of the test passed. + + globs + A dictionary containing initial global variables for the tests. + + optionflags + A set of doctest option flags expressed as an integer. + + parser + A DocTestParser (or subclass) that should be used to extract + tests from the files. + + encoding + An encoding that will be used to convert the files to unicode. + """ + suite = _DocTestSuite() + + # We do this here so that _normalize_module is called at the right + # level. If it were called in DocFileTest, then this function + # would be the caller and we might guess the package incorrectly. + if kw.get('module_relative', True): + kw['package'] = _normalize_module(kw.get('package')) + + for path in paths: + suite.addTest(DocFileTest(path, **kw)) + + return suite + +###################################################################### +## 8. Debugging Support +###################################################################### + +def script_from_examples(s): + r"""Extract script from text with examples. + + Converts text with examples to a Python script. Example input is + converted to regular code. Example output and all other words + are converted to comments: + + >>> text = ''' + ... Here are examples of simple math. + ... + ... Python has super accurate integer addition + ... + ... >>> 2 + 2 + ... 5 + ... + ... And very friendly error messages: + ... + ... >>> 1/0 + ... To Infinity + ... And + ... Beyond + ... + ... You can use logic if you want: + ... + ... >>> if 0: + ... ... blah + ... ... blah + ... ... + ... + ... Ho hum + ... ''' + + >>> print(script_from_examples(text)) + # Here are examples of simple math. + # + # Python has super accurate integer addition + # + 2 + 2 + # Expected: + ## 5 + # + # And very friendly error messages: + # + 1/0 + # Expected: + ## To Infinity + ## And + ## Beyond + # + # You can use logic if you want: + # + if 0: + blah + blah + # + # Ho hum + + """ + output = [] + for piece in DocTestParser().parse(s): + if isinstance(piece, Example): + # Add the example's source code (strip trailing NL) + output.append(piece.source[:-1]) + # Add the expected output: + want = piece.want + if want: + output.append('# Expected:') + output += ['## '+l for l in want.split('\n')[:-1]] + else: + # Add non-example text. + output += [_comment_line(l) + for l in piece.split('\n')[:-1]] + + # Trim junk on both ends. + while output and output[-1] == '#': + output.pop() + while output and output[0] == '#': + output.pop(0) + # Combine the output, and return it. + # Add a courtesy newline to prevent exec from choking (see bug #1172785) + return '\n'.join(output) + '\n' + +def testsource(module, name): + """Extract the test sources from a doctest docstring as a script. + + Provide the module (or dotted name of the module) containing the + test to be debugged and the name (within the module) of the object + with the doc string with tests to be debugged. + """ + module = _normalize_module(module) + tests = DocTestFinder().find(module) + test = [t for t in tests if t.name == name] + if not test: + raise ValueError(name, "not found in tests") + test = test[0] + testsrc = script_from_examples(test.docstring) + return testsrc + +def debug_src(src, pm=False, globs=None): + """Debug a single doctest docstring, in argument `src`'""" + testsrc = script_from_examples(src) + debug_script(testsrc, pm, globs) + +def debug_script(src, pm=False, globs=None): + "Debug a test script. `src` is the script, as a string." + import pdb + + if globs: + globs = globs.copy() + else: + globs = {} + + if pm: + try: + exec(src, globs, globs) + except: + print(sys.exc_info()[1]) + p = pdb.Pdb(nosigint=True) + p.reset() + p.interaction(None, sys.exc_info()[2]) + else: + pdb.Pdb(nosigint=True).run("exec(%r)" % src, globs, globs) + +def debug(module, name, pm=False): + """Debug a single doctest docstring. + + Provide the module (or dotted name of the module) containing the + test to be debugged and the name (within the module) of the object + with the docstring with tests to be debugged. + """ + module = _normalize_module(module) + testsrc = testsource(module, name) + debug_script(testsrc, pm, module.__dict__) + +###################################################################### +## 9. Example Usage +###################################################################### +class _TestClass: + """ + A pointless class, for sanity-checking of docstring testing. + + Methods: + square() + get() + + >>> _TestClass(13).get() + _TestClass(-12).get() + 1 + >>> hex(_TestClass(13).square().get()) + '0xa9' + """ + + def __init__(self, val): + """val -> _TestClass object with associated value val. + + >>> t = _TestClass(123) + >>> print(t.get()) + 123 + """ + + self.val = val + + def square(self): + """square() -> square TestClass's associated value + + >>> _TestClass(13).square().get() + 169 + """ + + self.val = self.val ** 2 + return self + + def get(self): + """get() -> return TestClass's associated value. + + >>> x = _TestClass(-42) + >>> print(x.get()) + -42 + """ + + return self.val + +__test__ = {"_TestClass": _TestClass, + "string": r""" + Example of a string object, searched as-is. + >>> x = 1; y = 2 + >>> x + y, x * y + (3, 2) + """, + + "bool-int equivalence": r""" + In 2.2, boolean expressions displayed + 0 or 1. By default, we still accept + them. This can be disabled by passing + DONT_ACCEPT_TRUE_FOR_1 to the new + optionflags argument. + >>> 4 == 4 + 1 + >>> 4 == 4 + True + >>> 4 > 4 + 0 + >>> 4 > 4 + False + """, + + "blank lines": r""" + Blank lines can be marked with : + >>> print('foo\n\nbar\n') + foo + + bar + + """, + + "ellipsis": r""" + If the ellipsis flag is used, then '...' can be used to + elide substrings in the desired output: + >>> print(list(range(1000))) #doctest: +ELLIPSIS + [0, 1, 2, ..., 999] + """, + + "whitespace normalization": r""" + If the whitespace normalization flag is used, then + differences in whitespace are ignored. + >>> print(list(range(30))) #doctest: +NORMALIZE_WHITESPACE + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29] + """, + } + + +def _test(): + parser = argparse.ArgumentParser(description="doctest runner") + parser.add_argument('-v', '--verbose', action='store_true', default=False, + help='print very verbose output for all tests') + parser.add_argument('-o', '--option', action='append', + choices=OPTIONFLAGS_BY_NAME.keys(), default=[], + help=('specify a doctest option flag to apply' + ' to the test run; may be specified more' + ' than once to apply multiple options')) + parser.add_argument('-f', '--fail-fast', action='store_true', + help=('stop running tests after first failure (this' + ' is a shorthand for -o FAIL_FAST, and is' + ' in addition to any other -o options)')) + parser.add_argument('file', nargs='+', + help='file containing the tests to run') + args = parser.parse_args() + testfiles = args.file + # Verbose used to be handled by the "inspect argv" magic in DocTestRunner, + # but since we are using argparse we are passing it manually now. + verbose = args.verbose + options = 0 + for option in args.option: + options |= OPTIONFLAGS_BY_NAME[option] + if args.fail_fast: + options |= FAIL_FAST + for filename in testfiles: + if filename.endswith(".py"): + # It is a module -- insert its dir into sys.path and try to + # import it. If it is part of a package, that possibly + # won't work because of package imports. + dirname, filename = os.path.split(filename) + sys.path.insert(0, dirname) + m = __import__(filename[:-3]) + del sys.path[0] + failures, _ = testmod(m, verbose=verbose, optionflags=options) + else: + failures, _ = testfile(filename, module_relative=False, + verbose=verbose, optionflags=options) + if failures: + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(_test()) diff --git a/server/serverpl/filebrowser/tests/ressources/100/plgrader.pltp b/server/serverpl/filebrowser/tests/ressources/100/plgrader.pltp new file mode 100644 index 0000000000..d7901a2c4a --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/plgrader.pltp @@ -0,0 +1,65 @@ +author=DR 2016-2017 + + +# ceci est une FEUILLE D'EXERCICES PL + +# pour avoir des exercies accessibles sous moodle +# vous devez écrire une feuille comme celle ci +# dans laquelle vous indiquez les exercices +# que vous souhaitez vous apparaitres + + +# Le nom affiché dans moodle de l'activité PL +# ceci est modifiable une fois que l'zactivité à été chargée +name= LE NOM DU PLTP + + +# Le texte suivant est affiché au lancement de l'activité +# Cela permet de mettre des liens vers les cours associés +# des rappels de cours +# etc. + + +introduction== + +# BIENVENUE + +Bonjour, +bienvenue sur la plateforme Premier Langage. + +Cette plateforme développée à Marne la Vallée vous permet de valider l'avancement de vos compétences en python. + +A chaque TP est associée une PL Activité que vous devez réussir. + +Si vous avez des difficultés avec un exercice demandez à votre chargé(e) de TP de vous aider. + +Cordialement + +L'Equipe Premier langage. + + +== + +# ceci est pour le moment inopérant +# dans un avenir futur la list des tags seras collecté +# sur les exos puis comparer à celle ci pour vérifier la cohérence +# remplissez cette variable pour expliquer ce que vous +# souhaitez enseigner/valider/vérifier dans cette feuille d'exo +tag=print|input|variables|identifieurs + + +concept=debug + +@ /TPE/function001.pl +@ /TPE/operator001.pl + + + + + + +title= Tests de plgrader + + + + diff --git a/server/serverpl/filebrowser/tests/ressources/100/plgrader.py b/server/serverpl/filebrowser/tests/ressources/100/plgrader.py new file mode 100644 index 0000000000..002307c1f4 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/plgrader.py @@ -0,0 +1,382 @@ + +# -*- coding: utf-8 -*- + +from feedback import Feedback, subnlbybr +import subprocess +from plutils import * +import json +def getOutput(inputstr=None): + # FIXME + pass + +def removespaces(s): + s= "".join(s.split(" ")) + s= "".join(s.split("\n")) + s= "".join(s.split("\t")) + return s + +def compare(s1,s2): + """ + returns 0 if equals + 1 if withoutspaces equals + 2 if s1 in s2 or s2 in s1 + """ + if s1 == s2 : + return 0 + elif removespaces(s1) == removespaces(s2): + return 1 + elif s1 in s2 : + return 2 + else: + return 10 + + + + +class Grader: + def __init__(self): + try: + self.pld= json.load(open("pl.json","r")) + + except Exception as e: + + self.fb = Feedback() + #self.fb.addFeedback( "# erreur de plateforme \n pl.json illissible\n") + self.fb.adddiv("err","# erreur de plateforme \n pl.json illissible\n") + self.fb.success = True + self.fb.flags="-Wall -ansi" + self.doOutput() + sys.exit(1) + self.fb = Feedback() + self.fb.success=True + self.fb.flags="-Wall -ansi" + + + def generate_feedback_compilation(self,flags, compil_state): + + if compil_state == "error": + gcc_state = 'Erreur' + else: + gcc_state = 'Réussie' + + compil_fb = "" + if compil_state == "error": + compil_fb += 'Il y a des erreurs dans votre programme.' + else: + compil_fb += 'Aucune erreur détecté.
' + + if compil_state == "error": + compil_fb += '
Erreur dans votre programme:
' + #compil_fb += self.terminal_code(gcc_msg) + + return compil_fb; + + def compilestudent(self): + EEE=None + + import py_compile + try: + x= py_compile.compile("student",doraise=True) + + except Exception as EE: + self.fb.addCompilationError("") + #self.fb.addFeedback(self.fb.generate_feedback_compilation(self.fb.flags,"error",str(EE))) + #errcompil=generate_feedback_compilation2(str(EE),self.fb.flags) ici + self.fb.adddiv("errcompil",self.generate_feedback_compilation(self.fb.flags,"error")) + self.fb.adddiv("errcompilfin",subnlbybr(str(EE))) + + if "compilehelp" in self.pld: + self.fb.adddiv("compilehelp",self.pld['compilehelp']) + #self.fb.addFeedback(self.pld['compilehelp']) + self.fb.success=False + self.fb.compilation=False + return False + else: + + #self.fb.addFeedback(self.fb.generate_feedback_compilation(self.fb.flags,"","")) + self.fb.adddiv("compil",self.generate_feedback_compilation(self.fb.flags,"")) + + return True # compilation ok + + def doOutput(self): + + if "showinput" in self.pld: # valeur sans importance + self.fb.showinput =True + if "failure" in self.pld and not self.fb.success : + self.fb.addFeedback(self.pld["failure"]) + #self.fb.adddiv("failure",self.pld["failure"]) + + + if "taboo" in self.pld and checktaboo(self.pld["taboo"],"student"): + self.fb.adddiv("ftaboo"," la liste des mots taboo "+self.pld["taboo"]+". Raté !\n
Ces mots ne doivent pas apparaitre dans votre solution !\n") + #self.fb.addFeedback(" la liste des mots taboo "+self.pld["taboo"]+". Raté !\n") + #self.fb.addFeedback(" ces mots ne doivent pas apparaitre dans votre solution !\n") + self.fb.success = False + + if "needed" in self.pld and checkneeded(self.pld["needed"],"student"): + self.fb.addFeedback(" la liste des mots obligatoires :"+self.pld["needed"]+". Raté !\n") + self.fb.addFeedback(" ces mots doivent apparaitres dans votre solution !\n") + self.fb.success = False + + + dico_response = { "success": self.fb.success , "errormessages" : "","feedback": self.fb.feedback(), "other": "","error":"XK11","execution": "","grade":"1"} + return(json.dumps(dico_response)) + + def expectedoutput(self): + if not "expectedoutput" in self.pld: + return False + expected = self.pld["expectedoutput"] + stdinput = self.pld["input0"] if "input0" in self.pld else None + if stdinput and type(stdinput) != type("") : + self.fb.addFeedback("WARNING illegal type in input value") + stdintput= str(stdinput) + self.compareExpectedOutput(expected,0,stdinput) + return True + + def compareExpectedOutput(self,expected,i,stdinput=None): + """ + Compare the output from student to expected argument + update feedback in consequence + return True if sucessfull + """ + tmp=0 + r,t = self.getStudentOutput(stdinput) + c=compare(expected,t) # si egal ou contient ce qui est attendu + if r and c < 3 : + if self.fb.showinput and stdinput : + self.fb.addOutput("input:\n"+stdinput) + #self.fb.addOutput(expected) + self.fb.adddiv("ventree output"+str(i),"Entrée :"+self.fb.resultat(subnlbybr(stdinput))+"Attendue:"+self.fb.resultat(subnlbybr(expected))+"Obtenue:"+self.fb.resultat(subnlbybr(expected))) + self.fb.success = True + elif r : + if "nohint" in self.pld: # don't tel the answer + self.fb.addOutput(expected) + else: + self.fb.adddiv("entree expected obtenu"+str(i),"Entrée: "+self.fb.resultat(subnlbybr(stdinput))+"\n Attendue: "+self.fb.resultat(subnlbybr(expected))+"Obtenue:"+ self.fb.resultat(subnlbybr(t))) + + self.fb.addExpectedOptained(t, expected) + #self.fb.addFeedback("\nDifférence ="+str(c)+"\n") + self.fb.success = False + else: # erreur d'execution r = False + #self.fb.addCompilationError(t) + for x,y in self.fb.div: + if y=="errcompil": + tmp=1 + if tmp!=1: + self.fb.addCompilationError("") + self.fb.adddiv("errcompil",self.generate_feedback_compilation(self.fb.flags,"error")) + self.fb.adddiv("errcompilfin",subnlbybr(str(t))) + if "compilehelp" in self.pld: + self.fb.addFeedback(self.pld['compilehelp']) + self.fb.success = False + return self.fb.success + + def getStudentOutput(self, stdinput=None): + # execute student + + return self.execute(["python3","student"],instr=stdinput) + + def getSoluceOutput(self, stdinput=None): + + r,out = self.execute(["python3","soluce.py"],instr=stdinput) + if not r: + self.fb.adddiv("info","probleme with the soluce") + self.fb.adddiv("info2",out) + #self.fb.addFeedback("Problemes with the soluce") + #self.fb.addFeedback(out) + return (r,out) + + def execute(self,args,instr): + try: + if instr : + encoded = instr.encode("utf-8") + else: + encoded = None + tt = subprocess.check_output(args,input=encoded, + stderr=subprocess.STDOUT) + return (True,tt.decode("utf-8")) + except subprocess.CalledProcessError as cpe: + return (False,cpe.stdout.decode("utf-8")) + + def inputoutput(self): + if not "output0" in self.pld: + return False + si="input0" + so="output0" + i= 0 + while si in self.pld and so in self.pld: + self.fb.asio=True + #self.fb.adddiv("Entree",self.pld[si]) + #self.fb.addInput("Entrée:"+self.pld[si]) + r = self.compareExpectedOutput(self.pld[so], + i,stdinput=self.pld[si]) + #if not r: + # return True + i=i+1 + si="input"+str(i) + so="output"+str(i) + return True + + def inputsoluce(self): + if not "soluce" in self.pld: + return False + si="input0" + i= 0 + while si in self.pld : + self.fb.asio=True + self.fb.addInput("Entrée:"+self.pld[si]) + r,soloutput = self.getSoluceOutput(stdinput=self.pld[si]) + if not r: + return False + r = self.compareExpectedOutput(soloutput,i, + stdinput=self.pld[si]) + if not r: + return True + i=i+1 + si="input"+str(i) + return True + + def getrandomgenerated(self,num): + r,out = self.execute(["python3","inputgenerator.py",str(num)],None) + if not r: + self.fb.addFeedback("Problemes with the input generator ") + self.fb.addFeedback(out) + return (r,out) + + def direct(self): + if not "direct" in self.pld: + return False + if not "expectedoutput" in self.pld: + return False + with open("student","r") as f: + x=f.read().split("\n")[0] + if x == self.pld["expectedoutput"]: # FIXME ASSERT + self.fb.success = True + else: + self.fb.success = False + if "#" in x: + self.fb.addFeedback("ne mettez pas de commentaires dans votre réponse") + if "" == x : + self.fb.addFeedback("sur la première ligne votre réponse") + + self.fb.addFeedback("la valeur attendu était "+self.pld["expectedoutput"]) + return True + + def generatorsoluce(self): + if not "soluceX" in self.pld or not "inputgenerator" in self.pld: + return False + if "numberofgenerator" in self.pld: + num=int(self.pld["numberofgenerator"]) + else: + num=4 + self.fb.showinput = True # random ... + for x in range(num): + + self.fb.asio=True + r,stdinput = self.getrandomgenerated(x) + if not r: + return False + r,soloutput = self.getSoluceOutput(stdinput=stdinput) + if not r: + return False + r = self.compareExpectedOutput(soloutput,x, + stdinput=stdinput) + if not r: + return True + return True + + def dopltest(self): + + if not "pltest" in self.pld : + return False + try: + with open("pltest.py","w") as pltf : + with open("student","r") as f: + print("",end="\n",file=pltf) + print('\"\"\"\n'+self.pld["pltest"]+'>>> \n\"\"\"',file=pltf) + print(f.read(),file=pltf) + except Exception as e: + + return False + + import os + os.environ['TERM']="linux"# bug in readlinehttps://bugs.python.org/msg191824 + if 'mode' in self.pld and self.pld['mode'] == 2: + r,out=self.execute(['python3','-B','-m','pldoctest','-f','pltest.py'],instr=None) + else: + r,out=self.execute(['python3','-B','-m','pldoctest','pltest.py'],instr=None) + + self.fb.success = r + if r : + res=0 + i=0 + #self.fb.addFeedback(out) + liste = out.split('debut') + for n in liste: + if n != "" and n[1]=="T": + self.fb.addsymbole(n,'resume',True) + elif n != "" and i!=(len(liste) -2): + self.fb.addsymbole(n,i,True) + i+=1 + + + #self.fb.adddiv("testT",out) + + + else: + #r,out=self.execute(['python3','-B','-m','pldoctest','-f','pltest.py'],instr=None) + #elf.fb.addOutput("

Echec de tests

") + res=0 + i=0 + #self.fb.addFeedback(out) + + liste = out.split('debut') + for n in liste: + + if n != "" and i!=(len(liste) -2): + if n != "" and n[1]=="T": + self.fb.addsymbole(n,'resume',False) + elif n.find('False') !=-1: + self.fb.addsymbole(n,i,False) + else: + self.fb.addsymbole(n,i,True) + i+=1 + + self.fb.addOutput(out) + return True + + def grade(self): + """ + direct + compile + expectedoutput ? + input/output + input/soluce + inputgenerator/soluce + input/soluce + pltest + """ + + if self.direct(): + return self.doOutput() + elif not self.compilestudent(): + return self.doOutput() + elif self.expectedoutput() : + return self.doOutput() + elif self.inputoutput(): + if "showinput" in self.pld and self.pld['showinput']: + self.fb.showinput=True + return self.doOutput() + elif self.generatorsoluce(): + return self.doOutput() + elif self.inputsoluce(): + return self.doOutput() + elif self.dopltest(): + return self.doOutput() + # Default response should by an plateforme error + # or a good answer to pass to next exercice + if "author" in self.pld: + self.fb.addFeedback("

Problème exercice mal défini

Contacter l'auteur: "+self.pld["author"]+"\n Passez à l'exercice suivant.") + else: + self.fb.addFeedback("

Problème exercice mal défini

Contacter l'auteur, ou l'administrateur \n Passez à l'exercice suivant.") + return self.doOutput() diff --git a/server/serverpl/filebrowser/tests/ressources/100/plgrader2.pltp b/server/serverpl/filebrowser/tests/ressources/100/plgrader2.pltp new file mode 100644 index 0000000000..d7901a2c4a --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/plgrader2.pltp @@ -0,0 +1,65 @@ +author=DR 2016-2017 + + +# ceci est une FEUILLE D'EXERCICES PL + +# pour avoir des exercies accessibles sous moodle +# vous devez écrire une feuille comme celle ci +# dans laquelle vous indiquez les exercices +# que vous souhaitez vous apparaitres + + +# Le nom affiché dans moodle de l'activité PL +# ceci est modifiable une fois que l'zactivité à été chargée +name= LE NOM DU PLTP + + +# Le texte suivant est affiché au lancement de l'activité +# Cela permet de mettre des liens vers les cours associés +# des rappels de cours +# etc. + + +introduction== + +# BIENVENUE + +Bonjour, +bienvenue sur la plateforme Premier Langage. + +Cette plateforme développée à Marne la Vallée vous permet de valider l'avancement de vos compétences en python. + +A chaque TP est associée une PL Activité que vous devez réussir. + +Si vous avez des difficultés avec un exercice demandez à votre chargé(e) de TP de vous aider. + +Cordialement + +L'Equipe Premier langage. + + +== + +# ceci est pour le moment inopérant +# dans un avenir futur la list des tags seras collecté +# sur les exos puis comparer à celle ci pour vérifier la cohérence +# remplissez cette variable pour expliquer ce que vous +# souhaitez enseigner/valider/vérifier dans cette feuille d'exo +tag=print|input|variables|identifieurs + + +concept=debug + +@ /TPE/function001.pl +@ /TPE/operator001.pl + + + + + + +title= Tests de plgrader + + + + diff --git a/server/serverpl/filebrowser/tests/ressources/100/plutils.py b/server/serverpl/filebrowser/tests/ressources/100/plutils.py new file mode 100644 index 0000000000..3b63e56b4f --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/plutils.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# plutils.py +# +# Copyright 2016 Dominique Revuz +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + + +""" +Fonction utiles pour les grader PL. + +""" +import json +import re +import sys + +dictrans={ "execution": "Exécution" ,"error":"Erreur","compile":"Compilation ","feedback":"Retours"} + +# le checktaboo doit être fait en debut de grader +def checktaboo(taboo,studentfile="student.py"): + """ + check taboo est brutal + il faudrais faire une analyse du code avec l'AST pour + être sur que les mots clefs sont vraiment des mots clef + pas des truc ou les loups 'bass' sont transformée en 'bbotom'. + """ + globaltaboo = False + ltaboo = taboo.split('|') + mots = open(studentfile,"r").read() # + for x in ltaboo: + reexp=re.compile(x) + if reexp.search(mots) : + globaltaboo = True + return globaltaboo + + +def checkneeded(needed,studentfile="student.py"): + """ + check needed est brutal + il faudrais faire une analyse du code avec l'AST pour + être sur que les mots clefs sont vraiment des mots clef + pas des truc ou les loups 'bass' sont transformée en 'bbotom'. + """ + globalneeded = False + lneeded = needed.split('|') + mots = open(studentfile,"r").read() # + for x in lneeded: + reexp=re.compile(x) + if not reexp.search(mots) : + globalneeded = True + return globalneeded + + + + +def feedback(success=True,**kwargs): + message="" + d= { "success": success , "execution" : "", "feedback": "", "other": "","error":""} + for k in dictrans.keys(): + if k in kwargs.keys(): + message += "# "+dictrans[k] + "\n" + message += kwargs[k] + "\n" + d["feedback"]=message + return json.dumps(d) + + +def dodump(dr,ev=0): + #for key in ['execution','feedback','error','other','error']: + # dr[key]= '
'.join(dr[key].split("\n")) + pld=getpldic() + if "help" in pld: + dr['feedback'] += pld["help"] + if dr["success"] and "taboo" in pld and checktaboo(pld["taboo"]): + dr["success"]=False + dr['feedback'] += "# TABOOOOO \n\n Vous avez utilisé un terme interdit \n\n"+pld['taboo'] + print(json.dumps(dr)) + sys.exit(ev) + + +# dépréciée +def success(message): + dico_reponse = { "success": True , + "execution" : "", + "feedback": "# Bravo ! \n\n vous avez réussit l'exercice\n"+message, + "other": "","error":""} + dodump(dico_reponse) + +# dépréciée +def compileerror(message): + """ + compileerror("les messages du compilateur pour l'execution ") + + """ + message = "\n\n".join(pldecode(message).split("\n")) + dico_reponse = { "success": False , + "feedback": "# Erreur de compilation \n\n Le compilateur à détecté une erreur\n\n il faut la corriger\n\n"+message,"errormessages" : "" , "other": "","error":"","execution":"" } + dodump(dico_reponse) + + +# dépréciée +def erreurdexecution(message): + """ + appellez cette fonction quand il y a une exception dans l'execution + i.e. stderr non vide + appeller avec la concaténation de stdout et sdterr + """ + dico_reponse = { "success": False , + "feedback": "# Erreur à l'exécution\n Il semble qu'une erreur de programmation\n s'est glissée dans votre code \n# la Sortie standard\n"+str(message),"errormessages" : "" , "other": "","error":"","execution":"" } + dodump(dico_reponse) + + +# dépréciée +def failure(message): + """ + Une erreur d'excution résultat non conforme aux attentes + le message contient le nombre de tests réussis et le test en échec + """ + dico_reponse = { "success": False , "errormessages" : "" , + "feedback": "#Mauvais résultat \n Il n'y a pas d'erreur dans votre code \n Mais il ne calcule pas le résultat attendu\n # Execution \n "+str(message), "other":"" ,"error":"","execution":""} + dodump(dico_reponse) + + +# dépréciée +def plateform(dexec,feedback="# Erreur Plateforme \n Un problème de la plateforme\\n parlez en au professeur\\n passez à l'exercice suivant"): + feedback += "\n# Execution \n" + dexec['stdout'] + feedback += "\# Erreurs \n"+ dexec['stderr']+"\n"+error + dico_reponse = { "success": True , "errormessages" : "","feedback": feedback, "other": "","error":"","execution": "" + } + dodump(dico_reponse,ev=1) + + +pldicsingleton=None + +def getpldic(): + ''' + getpdic return the dictionnary contained in the file "./pl.json" + ''' + global pldicsingleton + if pldicsingleton == None : + try: + pldicsingleton= json.load(open("pl.json","r")) + except Exception as e: + pldicsingleton = {"plateforme":False, + "stderr":e,"result":False, + "stdout":"PlateForme IO ERROR"} + return pldicsingleton + + + + +def pldecode(s): + if type(s) is str: + return s + else: + return str(s.decode(encoding="utf-8", errors="strict")) + + + + +def check_output(want, got, normelize=True): + """ + Return True iff the actual output from an example (`got`) + matches the expected output (`want`). + If True the second return is + 0 perfect fit + 1 no lines reduced to spaces + 2 Normalization of spaces ( \s* -> \s ) + 3 No Spaces in comparaison + + >>> check_output("éeè "," \\néeè ".encode("utf-8")) + (True, 1) + >>> check_output("é e è \\n ","é e è ".encode("utf-8")) + (True, 2) + >>> check_output("é e \t è \\n ","éeè") + (True, 3) + """ + + if type(got)== type(b'r') : + got = got.decode("utf-8") + if type(want)== type(b'r') : + want = want.decode("utf-8") + if got == want: + return True,0 + + # If a line in got contains only spaces, then remove the + # spaces. + got = re.sub('(?m)^\s*?$', '', got) + print(got) + if got == want: + return True,1 + # This flag causes doctest to ignore any differences in the + # contents of whitespace strings. Note that this can be used + # in conjunction with the ELLIPSIS flag. + if normelize : # we want normelized white spaces + got = ' '.join(got.split()) + want = ' '.join(want.split()) + if got == want: + return True,2 + # We didn't find any match; return false. + if True : # no white spaces + got = ''.join(got.split()) + want = ''.join(want.split()) + if got == want: + return True,3 + return False,-1 + + + + + +def createInputFile(pld,lastgenerated=True): + # il faut pour tous les input* verifier que l'execution de student celle de soluce + # ou bien faire inputgeneratorcalls appels à inputgenerator et verifier la même chose + """ + creates a file "input.txt" in current directory + with the inputgenerator if it exist + with the input field if it exist + with input0 to input9 FIXME in this order + the inputgenerator is considered random + and new file will create each call + + >>> import os.path + >>> if os.path.isfile("input.txt"): os.remove("input.txt") + >>> plk={"inputgenerator":"import random\\nfor n in range(10):\\n print(random.randint(4,123))","input":None} + >>> createInputFile(plk,lastgenerated=True) + True + >>> "inputgenerator" in plk + False + >>> os.path.isfile("input.txt") + True + >>> createInputFile({"inputgenerator":"import random\\nfor n in range(10):\\n print(random.randint(4,123))","input":"Toto"}) # ambiguité entre input et inputgenerator + Traceback (most recent call last): + ... + SystemExit: 0 + >>> if os.path.isfile("input.txt"): os.remove("input.txt") + >>> createInputFile({"input": b"1\\n2\\n3\\n4\\n"}) + True + >>> os.path.isfile("input.txt") + True + >>> createInputFile({"input4": b"5\\n5\\n5\\n5\\n"}) + True + >>> createInputFile({}) + False + """ + + if 'inputgenerator' in pld: + with open("inputgen.py","w") as ig: + print(pld["inputgenerator"],file=ig) + d=exectojson("inputgen.py") + if 'input' in pld and pld['input'] != None: + # TODO remonter une erreur a l'auteur du test + failure("INPUT ET INPUTGENERATOR AMBIGUITE\\n") + pld['input']=d['stdout'] # on écrasse le input + if lastgenerated: + del pld['inputgenerator'] # doit repondre faux la prochaine fois + elif not 'input' in pld: + for i in range(0,10): + s='input'+str(i) + if s in pld: + pld['input']=pld[s] + del pld[s] + break + + if 'input' in pld: + with open("input.txt","w") as it : + print(pldecode(pld['input']),file=it) + del pld['input'] + return True + else: + return False # retourne faux si pas de input ou si fin des inputs prédéfinis + diff --git a/server/serverpl/filebrowser/tests/ressources/100/soluce.pl b/server/serverpl/filebrowser/tests/ressources/100/soluce.pl new file mode 100644 index 0000000000..341d783cf0 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/soluce.pl @@ -0,0 +1,38 @@ +# Copyright 2016 Dominique Revuz + +# using the new plgrader + +# resrved for soluce problems + + + +grader== +from plgrader import Grader +g=Grader() +print(g.grade()) +== + +@/__init__.py +@/plgrader.py +@/feedback.py +@/plutils.py +@/pldoctest.py +@/template.html + + +type=sandbox + +# ce builder ne fait rien mais il a la bonne api +# il prend un de dic pl en parametre et retourne un dic de pl +build== +def build(bob): + return bob +== + + + + +# une interface standard d'exercice avec un editeur pour la réponse +form=@ /editorform.html + + diff --git a/server/serverpl/filebrowser/tests/ressources/100/template.html b/server/serverpl/filebrowser/tests/ressources/100/template.html new file mode 100644 index 0000000000..f64920b723 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/template.html @@ -0,0 +1,143 @@ +{% if feedback.compile %} +
+ {% for text,type in feedback.div %} + {% if type == 'errcompilfin' %} +
+ +

{{text}}

+
+
+ {% else %} +

{{text}}

+ {% endif %} + {% endfor %} +
+ +{% else %} + {% if feedback.asio %} + + {% if feedback.success %} + {% for text,type in feedback.div %} + {% if type == 'compil' or type == 'vresume' %} +
+

{{text}}

+
+ {% else %} +
+ +
+
+

{{text}}

+
+
+
+ {% endif %} + {% endfor %} + {% else %} + {% for text,type in feedback.div %} + {% if type == 'compil'%} +
+

{{text}}

+
+ {% elif type == 'fresume' %} +
+

{{text}}

+
+ {% else %} + {% if type[0] == 'v'%} +
+ +
+
+

{{text}}

+
+
+
+ {% else %} +
+ +
+
+

{{text}}

+
+
+
+ {% endif %} + {% endif %} + {% endfor %} + {% endif %} + {% else %} + {% if feedback.success %} + {% for text,type in feedback.div %} + {% if type == 'compil' or type == 'vresume' %} +
+

{{text}}

+
+ {% else %} +
+ +
+
+

{{text}}

+
+
+
+ {% endif %} + {% endfor %} + {% else %} + {% for text,type in feedback.div %} + {% if type == 'compil'%} +
+

{{text}}

+
+ {% elif type == 'fresume' %} +
+

{{text}}

+
+ {% else %} + {% if type[0] == 'v'%} +
+ +
+
+

{{text}}

+
+
+
+ {% else %} +
+ +
+
+

{{text}}

+
+
+
+ {% endif %} + {% endif %} + {% endfor %} + {% endif %} + {% endif %} +{% endif %} + diff --git a/server/serverpl/filebrowser/tests/ressources/100/template.pl b/server/serverpl/filebrowser/tests/ressources/100/template.pl new file mode 100644 index 0000000000..d7b633a279 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/template.pl @@ -0,0 +1,30 @@ +# Copyright 2016 Dominique Revuz + +# using the new plgrader + + + +grader== +from plgrader import Grader +g=Grader() +print(g.grade()) +== + +@/__init__.py +@/plgrader.py +@/feedback.py +@/plutils.py +@/pldoctest.py +@/template.html + +@ /builder/none.py [builder] + + + +# une interface standard d'exercice avec un editeur pour la réponse +form=@ /editorform.html + + + + + diff --git a/server/serverpl/filebrowser/tests/ressources/100/text.pl b/server/serverpl/filebrowser/tests/ressources/100/text.pl new file mode 100644 index 0000000000..31bf67bd5e --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/100/text.pl @@ -0,0 +1,27 @@ +# Copyright 2016 Dominique Revuz +title=fonction +author=Dominique Revuz +name= Une fonction bob +tag=function +template=../template.pl +text== +# Fonctions + +Ecrire une fonction **bob** qui retourne la valeur entière 1238. + + >>> bob() + 1238 +== + +code== + +# Fin du code, n'écrivez pas de code après cette ligne s'il vous plait ! +# L'équipe PL +== + +pltest== +>>> bob() +1238 +>>> bob()==1238 +True +== diff --git a/server/serverpl/filebrowser/tests/ressources/100/truc.pl b/server/serverpl/filebrowser/tests/ressources/100/truc.pl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/server/serverpl/filebrowser/tests/ressources/100/truc2.pltp b/server/serverpl/filebrowser/tests/ressources/100/truc2.pltp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/server/serverpl/filebrowser/tests/ressources/200 b/server/serverpl/filebrowser/tests/ressources/200 new file mode 160000 index 0000000000..43c7e2ff4a --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/200 @@ -0,0 +1 @@ +Subproject commit 43c7e2ff4a10563973c2d62ee81a94fbf36d68d6 diff --git a/server/serverpl/filebrowser/tests/ressources/300/HEAD b/server/serverpl/filebrowser/tests/ressources/300/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/server/serverpl/filebrowser/tests/ressources/300/config b/server/serverpl/filebrowser/tests/ressources/300/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/server/serverpl/filebrowser/tests/ressources/300/description b/server/serverpl/filebrowser/tests/ressources/300/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/applypatch-msg.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/commit-msg.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/post-update.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-applypatch.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-commit.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-push.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-rebase.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-rebase.sample new file mode 100755 index 0000000000..33730ca647 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-receive.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-receive.sample new file mode 100755 index 0000000000..a1fd29ec14 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/prepare-commit-msg.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..f093a02ec4 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/server/serverpl/filebrowser/tests/ressources/300/hooks/update.sample b/server/serverpl/filebrowser/tests/ressources/300/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/server/serverpl/filebrowser/tests/ressources/300/info/exclude b/server/serverpl/filebrowser/tests/ressources/300/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/00/2307c1f4b8ddb273c44f1f7a2f3692f8858c78 b/server/serverpl/filebrowser/tests/ressources/300/objects/00/2307c1f4b8ddb273c44f1f7a2f3692f8858c78 new file mode 100644 index 0000000000000000000000000000000000000000..970b996b59e8a4b871b5b2dc2f32ade22bf95733 GIT binary patch literal 3889 zcmV-156#V(!3+RiMEN1BDpEU_?w;;5-90_SWnM1D{L4Rn_4Ob2_Ql~B zhhkNxS+O}4ZL>c7np*bu)>XL`Ybn!Zvie12+g({T;#>Va5_P*Q^5xaC+S6TV*j439 z*0q|t%iAW)Ycul;Gw$cQEcQ~l78}{TY@1!%gjs=~byLMZl!ct0?g@PM#kVhh`hLOx z-z6Z*b1Jzl-^+TJfKOOs=%YFoK`{Hd%!;s{)w?`v!axMm>3Gk(;^wY~yDHhVRUtIL zRk__Il?>~7RG$#LAPDvzsWG)U7TH?JU)vKvOv+@g99v zIg~8EtCCb!$^vAg^Yg68n)CCpmiby+hOE<6S85P_p%3-Xc6k~LvhOS}lQb-MvIv7+ zKBJ}}3Mx{t8m@lYFY*thQ3TEAWk_6j`2Ub)7A9Nl@`5a7d=v`!E10Eb4b5S+^_NI9Sh{ zs+FT$*Lkw3qI&Twc zkA+j)ypR=CeQtcoIc=&8+J~VTR}mA6;Do6CylybsV$CiR8^Ikwn}AGER)G(c&1$7c zK%0{Vn|or3DDb*H0vNr%PPrg$$O;RUo?|GR@wAr%+DAENZo3Z!Gue3bZ;`b4;CH1};;@U3 z$4BbDIqV#IDk414zbCgvk{=f`wde&HIv*;vMkG?)T;}*`i1+{&Sz27*t5vG zt;xST21i7cr5y5#5 zVsBbz?-7dxPAekW;n<73wJ;2$T@!>=?zQ_h>(|i z?#1pNGHZb|q)czHtLk(Nt4H6jEtOAYg zJG*-s-7SVUgzr?H)nS^g%5yAYR~EHIlKXGC5OsbCPA$$2xkx9;TO2QvjjRK4it}<5 zm}K0UVw%D!CY8;FL}txn5%DR@4u1Oke2%H|Qmz=rx*NG_D$zX;{$piS-ZA8}VA^hX zb?8ZL94eg;js!VZgrdj~$!`Zt440$Oqbw_#N@jcdPTr#tvE!H=?4AZC>2WM9J zxednxCK z!G#C7e?EKl!;2rjqf@HfBstF~A(XfX7p?5!mVFOvTRAfCZiCtlH%7E?R6uPrjbS3dMQK2(Oay<6Y*sa zI)pjON0HInyn;s?fqLT^A9@0yrlnT^KSh)}F!MUA(-b#c^^oEZG{Ts;OA)e?#-2pJ z3bSbxEYN)YQCi0g`QhT)9JPSZ&CE3frw73^r5I^k`S=W@>nUcuTtb!Yy(f$>#5mSo zsYVs*ddekNQIr?he7?hi)zh+=G@_B%!$3C@ybrRnz%m=aQQoae9#6TQ+3HZGG;cH_ zN><0f1n~JP^BGsD#RC+IB*P5vt=G zm0fx@%9u&cc2~iWwSg_nMya8`tuU=deJ0>!#k#~wP*X)sAGLnEp<$m(Ir*xeE{)AT z2}oKW*>3!^C3%0 zdF%u5!IQ<^J>%^4n{QtJcq8?3neB#b?H$CQ()tr}@f=sGLRoio?QuT=fnZZjGPJ2v0@$3JYm(FDi|Z z0a!XOuaz(eq$$dPtFda6+C!@QQ(duM>Sq?KI73lyCrtjuZzm`it^Eym8Lx3n##4)_ zdHO(`4{Z@NkY?E<6VE@|~JIm`-%6Z~*R zL?KkL&&s{@*W+?T0@on}<@K$^f&X7zUB~MUY)UHkBwWpPO6({ia9y;A6XF<<9xg}H zAS1HJvBt#$4hB`i~$B;tZ(BHttV*bW;fTF(CF?Ai>)Z?DOoSrFa4JH11iLxz6&HT;KuwR4xW4LdXm5;fqEUZI>c!q)%Boyjac&`ku`#M5?0V?c(A$yO zPO30O84>u(s@oW_t9YsE43MpyotVvLb~K-!DLSHWBSZ38-4g96&xE7-PGyUj##4?s z*UQd$L=lP_IkQmDs9w3_Qv%?iL`xJN!qhD3uHbihSMYn7M4pow4PPOod%)r!kE|Ua zrl(-rp`YNCwQVW!Jgs=2Rb}yZ@@Dbs`^i}xP;&t7!-SA#TltYueXqsJpdUS;5 z8it@k#jLD0M|e0i|I_^Ilg=ps2a_$1w-YxJR!1Bhz}V}k{xgaTC!Gr*{c-}NGB=z4 zOmtmj5={>ON7N|-yhm#QrO zR2A4gFH@|VqtX->io$Ldi7NQ{fj%^RV*Kx2|aIg#mODmi8Mse{(~TNFSycr z_G!PM#*DSUROOrC$;HXj1yywDB`t|uKRH5EFY6khst+r#W^f~}x}}pe_d}Vu17hxyLD7P$9 zY(`f!#FwE(zi*(&du^25$Y$uR&LOGs<$0FT@XwD9Ozb!ExmUI71ZO3pJO<&^em56V z;DoZ1_>1k}MEFQG_Zn4x?Kj$N_h_uQ->AH{r9kr9F8~rNLZ3{XoRpTQm7qopUwJEF znCD!2u{!ezA3%!v_&N&vuN3Yh64POoDvkDK!J=1Ot`ppy12y`U5H!=Gz`Mk&bNX8c`ZpduW#sgAuk)cQ>s>(^9YeFn6${lDlgUcjl?ie5yX1=;K|?SPX%9@ zT>plP8ADlY@vtSm{s!I$H6I=||1-Xk{t@D$PH_ES z47N1uHhZ5G&CCf9V=y!@Ff%bxC`m0Y(JQGaVKBJvtJ>mp{8*^at6y76!g{Z> L9Wej^LT?bt?&1`g literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/0d/748883ebea63a3e7fcb393fb148132f1b368fc b/server/serverpl/filebrowser/tests/ressources/300/objects/0d/748883ebea63a3e7fcb393fb148132f1b368fc new file mode 100644 index 0000000000000000000000000000000000000000..336bfaed234657bd77928849b9936edb6a7f0886 GIT binary patch literal 241 zcmV4FlR6{FfcPQQAjK($jMAjEXmBz(<@0VVz|wG%Qv#&;-0MZ zMc$@0d~TZ#aqhydR4=`X;it##wX1|4Gfz&I*cvlEhC5dByCx3R6;%w=J;E+Kl>X+g zv%hiQw1W9ecSeUf7S&am1q=nJi>6FnX)2&|?evp!_ku@rKG$!+RBEILQtR}k{F3Z5 zkv?_CYZW!;SDxd_ez_S_tua{bZM{6{70)I&JDv&n@S$acS^{5jAf{Rq63op_ElC6` rH}SLE*V@4yy=~$$)dVlSiYK2~eq$P4P@0sJS)2jZfz6WuL8qprd6jb^ literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/server/serverpl/filebrowser/tests/ressources/300/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 0000000000000000000000000000000000000000..f74bf2335bbc5999ad0faff94fb04165d8ab5c7d GIT binary patch literal 21 ccmb~ZE#08nZNMgRZ+ literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/20/a243ba824ff3f9f36137a79dba04d016f19d21 b/server/serverpl/filebrowser/tests/ressources/300/objects/20/a243ba824ff3f9f36137a79dba04d016f19d21 new file mode 100644 index 0000000000000000000000000000000000000000..63672d43e1cdfcf6f83c719fd9f15b7ebc7ddc9f GIT binary patch literal 172 zcmV;d08{^X0j-Wp3c@fH0A1%4*$eXWZ61gyA}9!odV%!ig$k{a8W-MP(JQ!`Sq(#F z7_L=nrzUk|mR1qEoJ~S+0#lnJdIT4f)xmI+0YXC>jkw2=Z&gy(z%6(kQZ|O$Y|wkn zN;6n?9$Q4kL6eBj+LckRSNr`&uD7S%`EodJH*!uC`<|D5`LHXGI{R8GL%iM=QaNh@ a8t1K?f&viq9j^R`lo(#Wns@{L1Wuzb5>31S literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/26/1e9b13f23bcddf1366c6278d216a76ee57b0e0 b/server/serverpl/filebrowser/tests/ressources/300/objects/26/1e9b13f23bcddf1366c6278d216a76ee57b0e0 new file mode 100644 index 0000000000000000000000000000000000000000..5caf9a53307cad8fb60cd35e5f3fec99109e1ef4 GIT binary patch literal 462 zcmV;<0Wtn~0V^p=O;s>6G-EI^00M=O09OXhB|lc*etY=SQMt|b)+^0FCnxo58yXmx znV2ZV$7kkcmc+;F6;v`joBL??ri<5{y}LEnl}Ft;^rRIC@sGzS1+R^H;2K^bm8<53Q1>#OUozD{{6`Q z+52h7AZ|*nC@D%zE{QKmEiPf;E$L`}{VI9!^FNy>{}yR9`nWmc57fl8)YO!u#N=#{ zl37Oyvi3y%A4NwezLW_T_p6?S`3Ai3=GQb2fysNyQ%ny zzkHRx*`yz>Jr!umjEFF>v?Md9800?dO!sdhh#!ml2-uK(L6<=+8FS#13s3bMFASbaT6%xkZJQcPpw>=R#+TwC@>(WO4 zDgSm%KvxKiwd>oA7ghQcbZmUU>SK0XY;nW{`@=A0MWxB0h{Y56B|s%mKVnk_04Bl) EMC9M;HUIzs literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/29/a4f8abdbedc3f2c51eb33f3ba937f363628f2b b/server/serverpl/filebrowser/tests/ressources/300/objects/29/a4f8abdbedc3f2c51eb33f3ba937f363628f2b new file mode 100644 index 0000000000000000000000000000000000000000..430d005587d61e9e5021435b8deb70d46cbea458 GIT binary patch literal 127 zcmV-_0D%8^0V^p=O;s>7v}7`)Z!8b&M4MeuT;4sLKm_lAM4F#Y8Rb! z)zHAe%)~??tu!yWBr`wHz`#(iAcw(lfBN3IsdE2yRd%&j{I8Jv)4rh{sxrSIwJ5P9 hzX+zpw|b5n#GFmetEh;`=ZIzdH@m-s+E_3C896huMgboNVZ4Rh=Fu-Ju@0k~uy$G!iZs0*=hW$V zhMyz^XA^k+MKzpp_ELD|5yJ7?B#Doc1hzqeN3y}kt(MSAjllyNQr$k9X7j~;+CMqX zvaAntei7$h<*Y|>R@f4UDPom@GC(g~f|+*Tk=)`AoNS=PgAUU9Zr?zGM3Jbf$Ze)d uLu*3=v6mt^>nkjyJ*IOAtL4aYny@)kJW4Z!`4fZ~gm)is@cjUkxQL;(G>u~b literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/34/1d783cf03fb1894f710defb7b673d586ef19ab b/server/serverpl/filebrowser/tests/ressources/300/objects/34/1d783cf03fb1894f710defb7b673d586ef19ab new file mode 100644 index 0000000000..f93727ed64 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/objects/34/1d783cf03fb1894f710defb7b673d586ef19ab @@ -0,0 +1 @@ +xEQKn1 z/j/4A&04D EMިByW|b:>(kEsOw5|nP<"l2e,<)1눐9Y4B`nW 8Sڰ֛LQևYhnw߫eঢ়8!:k_l\|`%Ū2Dg[@籕|8"L0B0T<l4B1:QGd&0jTYUef5qfu8-6c߁Fυ, y2금`؁o#i8fH2dm߼9ł;ðHe3;kN \ No newline at end of file diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/34/4e3ebe85880b5bb691a625604a2e78e4f204fb b/server/serverpl/filebrowser/tests/ressources/300/objects/34/4e3ebe85880b5bb691a625604a2e78e4f204fb new file mode 100644 index 0000000000000000000000000000000000000000..ee78d051dccd88831365056b8e4df4eb1f529c6a GIT binary patch literal 319 zcmV-F0l@xv0ZYosPf{>3GhztvW@Zs#U|`^2&s%n7t0H?<@YVe;`6I?-o=7|rC^Gmcz`9C%nSte&!l zd+tl8Bgz%6L0%gikNXMF^kfx$pXXE_`|rE`f9`xOQ_jOnCEG5TO%Q*k{@tKYEN{~~ z&%Mk`S1oJnDi=O6>7aURz<1^s$>M&oHoG4$7TriAdWdm8y R1cXb0v@pm;3;>_3g6)s9lOg~B literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/3b/63e56b4f753a79d1a4b9e8ebd0f185dfca3774 b/server/serverpl/filebrowser/tests/ressources/300/objects/3b/63e56b4f753a79d1a4b9e8ebd0f185dfca3774 new file mode 100644 index 0000000000000000000000000000000000000000..c8affa0952cc73508c017e23cd9aeae9cdb5aecc GIT binary patch literal 3637 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 Q2uKMuB(SLh0CoKr4BCzGxBvhE literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/42/f477d21de6148e2701d6787ccfa9ce0a6be9b3 b/server/serverpl/filebrowser/tests/ressources/300/objects/42/f477d21de6148e2701d6787ccfa9ce0a6be9b3 new file mode 100644 index 0000000000000000000000000000000000000000..cdee30688fa509f98bc8b7ad0a92094a76504706 GIT binary patch literal 209 zcmV;?051P{0ZYosPf{>3GhqnuW@Zs#W?&&q3kq^FlM_oa z^Yiqwiu3bUhn>30#7lQ&=1tWt51H*a0(?^)EFgEmt{dNJ;Fn)kH%m!?pfY~6>@Yc~9 zXcovz$fh>jvadP@GOh3in1=BYrecPw2m{Pikh^1{rV{0LsBH*aSlK`xWClVxMh1pC LAPxfnB(ZL1+XY&( literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/43/c7e2ff4a10563973c2d62ee81a94fbf36d68d6 b/server/serverpl/filebrowser/tests/ressources/300/objects/43/c7e2ff4a10563973c2d62ee81a94fbf36d68d6 new file mode 100644 index 0000000000000000000000000000000000000000..e3908d3372104b7da932b6f286eda6f8eee271c3 GIT binary patch literal 146 zcmV;D0B!$x0j-Wp3c@fH0A1%4Z!gG8nvaDdN-4BZP}B>g$tw+{4b*slrC!0+%xV~y z<~d{#Snl#l1VVEv2VRV#7@YSbFSK@An4=e5Tce4T8IX|Kcb?J;^`-9{sM~w@cwTR9 z1C?_&Mmopz<&<9eu$fP@4a*6*k`mDpI8sYKEi@lqdqg5#b*To8(!c-na7*ui%Cpvr$| z9Jgg*D{-S$E6GlN|%nXxK@mfHw_t3sUz{6w(_DX?jm^AxnxCi%v$R4hmPSFjkrlE z-KY2A3(hDh77#<}P6}W37cQ>SC__1SZ;Ns&@Zz>y$kxr_O|xG>g>qAQGN#jD{)x?~ zBbr&Rsf|{k_Bq#Xhc?fF>p;U_&f66*MzkD{&tqLjNT?jfcxePe^l3o_hTU>Pvj+;K zZd%`yur{{N{r+5*fj=}vMkNI`!$9+ztY?=_lnLn?#Se2@jm8CoEmtjF|8MgI@|J}S z|E{#M@!)LP*FVW;#7mseFMD(&n*ThV$Gx9C@-2^949bdIhknIczfM1}`{g7?{((#| zX-zCE>HN9Gm0Usr`fN#$Cn2sNG!=|w6kVh7BHoH-M%{d=u*QVZrY-(xkxH}rKly*z4_L(8J-auZ)#H zQRKnhi|QPIDz)Z7_jHinNoJQ0!qmTKkD^<_%Vg9azg-vC_X`yv{Y1wG{b%gwNXD7b z38Rha4YF6TZguHtzGMu?SSzIU1v1iOat(mcz6Pl#<1<%XI6T?<&0z2gT3my<#X1!4 literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/5c/2951fa0274d4c97b90876f7e05cfddccbd46ed b/server/serverpl/filebrowser/tests/ressources/300/objects/5c/2951fa0274d4c97b90876f7e05cfddccbd46ed new file mode 100644 index 0000000000000000000000000000000000000000..3cb9078d7028dc6496f05628422687fca35cdf5c GIT binary patch literal 127 zcmV-_0D%8^0V^p=O;s>7v}7`)Z!8b&M4MeuT;4sLKm_lAM4F#Y8Rb! z)zHAe%)~??tu!yWBr`wHz`#(iAcsMM=Z8^)(!O&qS1k&!>Th|aJ>NndsxrSIwJ5P9 hzX+;w>7K`bRa0+#i3pA2$#=RC;MLN76#(oMFM{nMI#mDw literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/70/cb729495a935102cd6cbe4774770e29cf37fb0 b/server/serverpl/filebrowser/tests/ressources/300/objects/70/cb729495a935102cd6cbe4774770e29cf37fb0 new file mode 100644 index 0000000000000000000000000000000000000000..107f446620e06cfbdfe5038f3c38903dac2f67b9 GIT binary patch literal 137 zcmV;40CxX)0ZYosPf{>4GG++yW@Zs#-~htDSu=eYpg;gf3os}!Bo-9pWF{w;Waj7T zWfkY=g@*7lu;1D7APR;{E4UdLSza(RFo22GVW+gtoe$pB6tFc0Xf!)V$L(?jUa(;d rKt3ar2s6SmWI2#!FtDT%#3I$|0B=?{kUmBrj04iyAPxfn`7k?N3F$X6 literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/71/5dc2fb52ebc112831673ddf4e5a02bd134957f b/server/serverpl/filebrowser/tests/ressources/300/objects/71/5dc2fb52ebc112831673ddf4e5a02bd134957f new file mode 100644 index 0000000000..e2d5fd3bdd --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/objects/71/5dc2fb52ebc112831673ddf4e5a02bd134957f @@ -0,0 +1,2 @@ +xSKo@laHU51I@aģ (Mz݇UP;ǘM?f=f ,_.VON\yxX{euC^wm0j|Z7y&B[gFtН ko`,ϼrb4pF夥a[eo1֡z~vjU5wC+As<˳H<8^_Pj-tu+cp@_ N+zᜌĈ9 Whl齕 *vMQ߃0]#`MR¾=nɓ0ĉ jEW41x*}C|tҥ>YNil^mɔ&Ӊ9#j2ں'$%GVPIT,-'e ,K0g֪ )i5AKFΨK Ad b/_i# B$bO[h:z~@ʈs>frX̊ճC\͈y&]^_^U9w$˳?D઺ \ No newline at end of file diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/76/1d782254146333d0dd9cbc7a4f56f7d2b7195f b/server/serverpl/filebrowser/tests/ressources/300/objects/76/1d782254146333d0dd9cbc7a4f56f7d2b7195f new file mode 100644 index 0000000000..a8419b6746 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/objects/76/1d782254146333d0dd9cbc7a4f56f7d2b7195f @@ -0,0 +1 @@ +xeAN0EY#yQDC,8NKNzp\ 'R?%<v7}jZ,óQC㉞Q}[K⯏y54:kidFv 2/QSye pxPjpa"Vv~pTz!u:O/> BmC+I9{H 19$Y$sP@Qױ.MC_sd7}{”~+b.vYs5,A?} \ No newline at end of file diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/7e/049dd66cd2ca9c295de1a18fa6a2253fe92ba9 b/server/serverpl/filebrowser/tests/ressources/300/objects/7e/049dd66cd2ca9c295de1a18fa6a2253fe92ba9 new file mode 100644 index 0000000000000000000000000000000000000000..44f067eae50d98bbc93d33cbb705aac9a52c1a88 GIT binary patch literal 128 zcmV-`0Du2@0V^p=O;s>7v}7`)Z!8b&M4MeuT;4sLKm_lAM4F#Y8Rb! z)zHAe%)~??tu!yWBr`wHz`#(iActY@y7fETe7+cjuA97X!}@E-FS_hs0#%t`kXn>j il3xT>Sr~ihchKvDLd{~ucfUMcpncI~YCQmZ@iOC`M?GTz literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/94/c8be18a32d8d8d27aefcaca6c4529c3fc0cf5a b/server/serverpl/filebrowser/tests/ressources/300/objects/94/c8be18a32d8d8d27aefcaca6c4529c3fc0cf5a new file mode 100644 index 0000000000000000000000000000000000000000..2a6236708eff7d19ee40b73d304fe5325ecaa026 GIT binary patch literal 145 zcmV;C0B-+y0j-Wp3c@fH0A1%4Z!gHpueDG_DTNjaih6;UPiY`+pvLRmd z&mn`rYL{2vr~N~a}G5-i^W&4DK+pEr+2aZWdq>(iLHz8W7YRFe9e_#ZjKym^c6>|vQAvoi%= z>FcIZuj#_ZM0wv2k@1{|a+7A&+>n$shEj^53jsmL;T4Km+JAKYfPy>q3>c+Q`4R0f zNZu1&%YZ;A0S{Q&E7gu_oe+xF1#{wuP?XNDt)&Exj0l5Ky8~_0SQ`~^A08nuUMF0Q* literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/c2/a65b6d8d988e0e026ab0742c9269b99947736c b/server/serverpl/filebrowser/tests/ressources/300/objects/c2/a65b6d8d988e0e026ab0742c9269b99947736c new file mode 100644 index 0000000000000000000000000000000000000000..476d7a043d97f507c183cb46ddc2bed2649a38c8 GIT binary patch literal 176 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%0A}))3() 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 P0#X7E32dqW@D~?CXA|BB literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/cf/20eb0628df33ebf39662e12e04e569b991e82c b/server/serverpl/filebrowser/tests/ressources/300/objects/cf/20eb0628df33ebf39662e12e04e569b991e82c new file mode 100644 index 0000000000..056d377911 --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/objects/cf/20eb0628df33ebf39662e12e04e569b991e82c @@ -0,0 +1 @@ +xOJ0_}=mAeYXaEQd[h%[VL)15d_q&v452rjv5fyA*`%oDQO6>mMts{vx=}y|1@$Y++ z2@qIC4dLYf=D4#l87Vx4hVm)?Flna5Xaam41z?ET6$k@8ZR?cYZZc#{TmU{~n3r;c zb5c|%r8#Y5aCHJ2&W=Wf`OE9-_OZHss4|?F&i+B>Psc}d)a%HCh8EJ`u8fSdaA-|T z12vk0m`J;nOUl{DXqDbxs<)etjPo9d31E0514M{KkJ1?4%X}VY?WEB)l7cS4c0j0e zJZ*zCk+2P&SmFOd@;2AKU|Lu$vuQzk2T5UcIuRE=`Msu!YQuJupzV;;-0PjbSkH_k zgaJ7v1LA>mM+yq*C5ju3v^7ir6>-ebP8=WTBZH4-uolJgR0SdzYAsFXp243Cs;8v= z0Z%9+rU{^yCJT*AZ@B;yHN zDvZW~nP0|r4Wxa71sHA_zy+q;J6Po69qPd`Oq0YlxGgX#oCccksMP`#m9@~xP^^_B zX^m4b5OCVNJD2Yb4uO+iS_;FR7KVp%dAgE!|Anas==k^uV~u=(v|qIKDZcE>wl4tQ zM(!3?DF#pu&H6a1X`Yt6^+OD2(`vX#Hk;z~^z6i&e%nEvGP|kh|2%~ literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/d7/b633a2794c7088b1e0054c36c716a358903fc3 b/server/serverpl/filebrowser/tests/ressources/300/objects/d7/b633a2794c7088b1e0054c36c716a358903fc3 new file mode 100644 index 0000000000000000000000000000000000000000..9d955db36f77ac9fc05482c505cc7916b78857a0 GIT binary patch literal 273 zcmV+s0q*{I0Y#9@O2t47hFur>4uP}qoQ16yI3QRlD7bekQl_1>9h{qKrh0rMpTie( zda*Jv%$NL0{!Fc14L7&PvmHD*e2;8r%9PtRdmEvmHE?zf74cM5oIl!w=El3>0u-38peTFW;9+4#;j+f=kAc#LFs5d zx6Wde!OI$WT~?XbwFuE2QY#vSfrXWVB*=68L~2xwi4hwJ7Emh2K;T`xv;g|~_RbC% XKf;kIO2{XLqlq>X%b?^JVvT=aMJj#^ literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/db/03da4d5980d1bc6a67a24b357c0e46b2c209ba b/server/serverpl/filebrowser/tests/ressources/300/objects/db/03da4d5980d1bc6a67a24b357c0e46b2c209ba new file mode 100644 index 0000000000000000000000000000000000000000..d5285dd6d9d640b5103d34efdbd771338fd7f10d GIT binary patch literal 224 zcmV<603ZK&0qs>w3d0}}b=^})4$zMO(%U4>qLvVYNmr$J??@8pA}JJN5UQI2N1Qir ze7>r26&30%N!fLcy_SP@?NIvTy>~6ydI5lzk`iD-7#$)PT49_Q2Z|{MQZsEN1*Q~8 zLSbtlOZPo`>>Hu@%H5oI_0pNJ$88I2Es6VMFLP7$%gLlt&mxSxxQyX-ND5pA6PMSWdUbr2nV@QU68-zhCoznEhX9Ar<<+0K*7X aQBh5o|GEF~L2<5;8ORKz$-o=lj9edwV{F<0 literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/db/074db632fe68242ab97f311c5b4afab0be3a3d b/server/serverpl/filebrowser/tests/ressources/300/objects/db/074db632fe68242ab97f311c5b4afab0be3a3d new file mode 100644 index 0000000000000000000000000000000000000000..bf17da78850a371595554fef08c7db6dc20a64df GIT binary patch literal 474 zcmV<00VV!;0V^p=O;s>6HDNF@00M=O09OXhB|lc*etY=SQMt|b)+^0FCnxo58yXmx znV2ZV$7kkcmc+;F6;v`joBL??ri<5{y}LEnl}Ft;^rRIC@sGzS1+R^H;2K^bm8<53Q1>#OUozD{{6`Q z+52h7AZ|*nC@D%zE{QKmEiPf;E$L`}{VI9!^FNy>{}yR9`nWmc57fl8)YO!u#N=#{ zl37Oyvi3y%A4NwezLW_T_p6?S`3Ai3=GQb2fysNyQ%ny zzkHRx*`yz>Jr!umjEFF>v?Md9800?dO!sdhh#!ml2-uK(L6<=+8FS#13s3bMFASbaT6%xkZJQcPpw>=R#+TwC@>(WO4 zDgSm%KvxKiwd>oA7ghQcbZmUU>SK0XY;nW{`@=A0`SD4q@dc$hIr!qgq^L9*6#aOT Q2uKMuB(SLh07RY_!Zr->fdBvi literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/db/2e6e1ba8e6938341cc50f0f084b026600e7351 b/server/serverpl/filebrowser/tests/ressources/300/objects/db/2e6e1ba8e6938341cc50f0f084b026600e7351 new file mode 100644 index 0000000000000000000000000000000000000000..fa7065eb775774071d0476a2c7f59cb313c6ea0d GIT binary patch literal 295 zcmV+?0oeX{0ZYosPg1ZnF=YtwW@h1H0D&cC*L=VXD8b7h!;n}|kdv95Sdy8aXRIF@ z!pXpF@=+(+42VlBxEUB(UNAE-fC-Q>B0!BC49s!od^&EIEARq&0zfQ>ZVa-SdRfK! zd0=y|St>2};5)EopQ|Hxk)=s6T*qVDSS!J7iJpScAt7BCNm_orsWm>nH&94YJGi tA(0DoIVf^T2pvTfmq%kW7Req0u4iQf`ka9k2yXxlIt&VN1^`cJ--B&rdyN19 literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/server/serverpl/filebrowser/tests/ressources/300/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 0000000000000000000000000000000000000000..711223894375fe1186ac5bfffdc48fb1fa1e65cc GIT binary patch literal 15 Wcmb0qt4IZrd;r?KxjTCc2RzNyq}j;k~7?ibc-PAoIO z53<$AyZhR7vmtFblFKQ$-0Bc#AZM9IXK8!;3@~x%wCuO$`0B?hq_L#jt*EGUj!KTx zlt?)vw9;v+m2v6LV>wUpX~DUR0N#LX!0fdkay%jnaCIyi^Skc=s;`0&=J7oXAUoSA zcD(?CvxQ9U&TVMwpm9@Lt3L3U6-`B{sJ6;T<>Ya{-Tqi9&Wvq!kTeeoN^>lwepC$U zPphBv^vc|oI7@;m0=^kmxTW}T2ys_iDaVB*Nja>8EK$8=J0l~mJyJtzG6~2Aye^1T z=t7K%cgOC&m3Zc)u*O{mhBWIXwY}Worh@qw0~5ob)J2L#hTyAJ>Y$UjNVbqJP{k6F zvzaZH#HHz3{ID`C{8Mw$%P|5VR>M+#yb_*ZB7j;w+)!AlE+i_WB_X{C~lG40<(j~^eRLc|L_$1m=q@{->ysm6ejVY1+#)&Q}ZZ~N2HVS03|NtPen?DN*V(^nOO zpy0W_IXmvD7^}n{x7S0}26xlCHu%~l=tS#jT{krlXV2}H!NuTude~kbXH0-S-_$_i z_kLFQv+qa+0RhYaGyh_2Z(<-ZIW+(uiwFRvY9dFxEfeQRN@6 zj-@l(zks3w0 z@qYb|6h+BGvSpY6!m#g)&ix;c5QLBuL6^bL&Cw-72xPax)OUNbT zV!we_7t2@XpWuQ{pg?3N3?xrwj{d+Dw$$DN|9S)qE2GJz*FI z+wFFA7JmII!g(;e$<%5)^B@COs9Pl zKP3zE*6)*lNS|np_d21V8&>mJOBp{VQ-QSMGO<_LFAZrv+s9kE~ zgRaVO0ig=#kTy_s!R~ZB?LF2w=ER~RR+zGg^5vj`{b6F7GWMJC)W&onu=9Hw+^NfP zT9aWcib5Nva4}SyS23``nC(-m$8^%ZKAo?_PY9h7$LQSpa9?Atk$KPlD-}8gPa*qM z>y(TCK_;Dor;zoYJwm$Z4WCApa@~tY9@CdZSDU5|Au%2Cd~(2;r=t|RSe|2COO5K- zrQ&wz%@3v5ZcbGuOLXcf_N0A5M%f8AX7DqgFuvJ= zn{8mR56lZH3S;@~IJ>0j9`X5tWWW5n`8LQS&C-96EY2jaUynuI61nnu8)iWtW9!w( z2`6GlWk1YlehG#u>SYqU0ciKwdFDfvwOUJ&%d$-CrVp2M-G+?zGQx=KvB)?Z`a+2_ z$u+LK_=gOMht}Vv(KhcKGIspCP`8#I5G85vguyX@usHSnrq1QPj9MJ?YS_&%>84pWGmQ`0wvYvoyCURnf2## xcC#YYTiW_`9|*j6-n?-r%sSdl4D3=egkxyn0qrqfJ-AewIMQPQ!Qaio)dlvP-ZcOK literal 0 HcmV?d00001 diff --git a/server/serverpl/filebrowser/tests/ressources/300/refs/heads/master b/server/serverpl/filebrowser/tests/ressources/300/refs/heads/master new file mode 100644 index 0000000000..4060e2869c --- /dev/null +++ b/server/serverpl/filebrowser/tests/ressources/300/refs/heads/master @@ -0,0 +1 @@ +c2a65b6d8d988e0e026ab0742c9269b99947736c diff --git a/server/serverpl/filebrowser/tests/views/test_load_pltp.py b/server/serverpl/filebrowser/tests/views/test_load_pltp.py index 2b2eca4d80..8199864ca8 100644 --- a/server/serverpl/filebrowser/tests/views/test_load_pltp.py +++ b/server/serverpl/filebrowser/tests/views/test_load_pltp.py @@ -3,10 +3,12 @@ from django.conf import settings from django.contrib.auth.models import User -from django.test import Client, override_settings, TestCase +from django.test import Client, TestCase, override_settings from django.urls import reverse from filebrowser.models import Directory +from loader.loader import load_file +from playexo.models import Activity FAKE_FB_ROOT = os.path.join(settings.BASE_DIR, 'filebrowser/tests/tmp') @@ -41,67 +43,64 @@ def tearDownClass(cls): def test_load_pltp(self): response = self.c.get(reverse("filebrowser:option"), { - 'name': 'load_pltp', - 'path': 'Yggdrasil/working.pltp', + 'name': 'load_pltp', + 'path': 'Yggdrasil/working.pltp', }, content_type='application/json') self.assertContains(response, "http://testserver/playexo/activity/1/", status_code=200) def test_load_pltp_no_path(self): response = self.c.get(reverse("filebrowser:option"), { - 'name': 'load_pltp', + 'name': 'load_pltp', }, content_type='application/json') self.assertContains(response, '"path" parameter is missing', status_code=400) def test_reload_pltp(self): - response = self.c.get(reverse("filebrowser:option"), { - 'name': 'load_pltp', - 'path': 'Yggdrasil/working.pltp', - }, content_type='application/json') - self.assertContains(response, "http://testserver/playexo/activity/1/", status_code=200) + pltp = load_file(self.dir, "working.pltp")[0] + a = Activity.objects.create(name=pltp.name, pltp=pltp) response = self.c.post(reverse("filebrowser:option"), { - 'name' : 'reload_pltp', - 'path' : 'Yggdrasil/working.pltp', - 'activity_id': 1, + 'name' : 'reload_pltp', + 'path' : 'Yggdrasil/working.pltp', + 'activity_id': a.pk, }, content_type='application/json') self.assertContains(response, "recharg", status_code=200) def test_reload_no_path(self): response = self.c.post(reverse("filebrowser:option"), { - 'name' : 'reload_pltp', - 'activity_id': 1, + 'name' : 'reload_pltp', + 'activity_id': 1, }, content_type='application/json') self.assertContains(response, "parameter 'path' is missing", status_code=400) def test_reload_no_activity_id(self): response = self.c.post(reverse("filebrowser:option"), { - 'name': 'reload_pltp', - 'path': 'Yggdrasil/working.pltp', + 'name': 'reload_pltp', + 'path': 'Yggdrasil/working.pltp', }, content_type='application/json') self.assertContains(response, "Missing 'activity_id' parameter", status_code=400) def test_test_pl(self): response = self.c.get(reverse("filebrowser:option"), { - 'name': 'test_pl', - 'path': 'Yggdrasil/working.pl', + 'name': 'test_pl', + 'path': 'Yggdrasil/working.pl', }, content_type='application/json') self.assertContains(response, "UPEM - PL", status_code=200) - + def test_test_pl_no_path(self): response = self.c.get(reverse("filebrowser:option"), { - 'name': 'test_pl', + 'name': 'test_pl', }, content_type='application/json') self.assertContains(response, '"path" parameter is missing', status_code=400) - - + + def test_test_pl_unknown_path(self): response = self.c.get(reverse("filebrowser:option"), { - 'name': 'test_pl', - 'path': 'Yggdrasil/unknown.pl', + 'name': 'test_pl', + 'path': 'Yggdrasil/unknown.pl', }, content_type='application/json') self.assertContains(response, 'No such file or directory:', status_code=400) From ecea64111e899a744434ed19b801d452d0e97f0d Mon Sep 17 00:00:00 2001 From: pl-test Date: Mon, 4 Feb 2019 12:19:44 +0100 Subject: [PATCH 6/7] updated requirements --- server/serverpl/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/serverpl/requirements.txt b/server/serverpl/requirements.txt index 93b0e294a9..46acea9853 100755 --- a/server/serverpl/requirements.txt +++ b/server/serverpl/requirements.txt @@ -15,7 +15,7 @@ markdown mkdocs mock oauth2 -psycopg2-binary +psycopg2 pydenticon pytest pytest-django From a48f64e7a61273b0cbda827f65fd3e028f1e5c61 Mon Sep 17 00:00:00 2001 From: Pavell Date: Mon, 4 Feb 2019 15:26:53 +0100 Subject: [PATCH 7/7] added name from pl to commit --- .../filebrowser/tests/views/test_git.py | 44 ++++++++++++++++++- server/serverpl/filebrowser/views.py | 10 ++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/server/serverpl/filebrowser/tests/views/test_git.py b/server/serverpl/filebrowser/tests/views/test_git.py index a296bf28bc..3988b83680 100644 --- a/server/serverpl/filebrowser/tests/views/test_git.py +++ b/server/serverpl/filebrowser/tests/views/test_git.py @@ -45,10 +45,18 @@ class GitTestCase(TestCase): @classmethod def setUpTestData(cls): - cls.user = User.objects.create_user(username='user', password='12345', id=100) + cls.user = User.objects.create_user(username='user', password='12345', id=100, + email="test@test.com") cls.user2 = User.objects.create_user(username='user2', password='12345', id=200) + cls.user3 = User.objects.create_user(username='user3', password='12345', id=300, + first_name="First", last_name="Last", + email="test@test.com") cls.c = Client() cls.c.force_login(cls.user, backend=settings.AUTHENTICATION_BACKENDS[0]) + cls.c2 = Client() + cls.c2.force_login(cls.user2, backend=settings.AUTHENTICATION_BACKENDS[0]) + cls.c3 = Client() + cls.c3.force_login(cls.user3, backend=settings.AUTHENTICATION_BACKENDS[0]) cls.folder = Directory.objects.create(name='Yggdrasil', owner=cls.user) cls.lib = Directory.objects.create(name='lib', owner=cls.user) @@ -169,6 +177,38 @@ def test_commit(self): self.assertEqual(response.status_code, 200) self.assertIn(b"mycommit", command("git log", dir=os.path.join(self.folder.root, "folder1"))[0]) + self.assertIn(b"user ", + command("git log", dir=os.path.join(self.folder.root, "folder1"))[0]) + + + def test_commit_no_mail(self): + response = self.c2.post( + reverse("filebrowser:option"), + { + 'name' : 'git_commit', + 'commit': 'mycommit', + 'path' : 'Yggdrasil/folder1/TPE/function001.pl', + }, content_type='application/json' + ) + self.assertContains(response, "User must have an email", status_code=400) + + + def test_commit_name(self): + with open(join(FAKE_FB_ROOT, 'Yggdrasil/folder1/TPE/function001.pl'), 'w') as f: + print("test", file=f) + response = self.c3.post( + reverse("filebrowser:option"), + { + 'name' : 'git_commit', + 'commit': 'mycommit', + 'path' : 'Yggdrasil/folder1/TPE/function001.pl', + }, content_type='application/json' + ) + self.assertEqual(response.status_code, 200) + self.assertIn(b"mycommit", + command("git log", dir=os.path.join(self.folder.root, "folder1"))[0]) + self.assertIn(b"First Last ", + command("git log", dir=os.path.join(self.folder.root, "folder1"))[0]) def test_commit_no_path(self): @@ -353,7 +393,7 @@ def test_show(self): 'path': 'Yggdrasil/folder1/TPE/function001.pl', }, content_type='application/json' ) - self.assertContains(response, "test\ntest2", status_code=200) + self.assertContains(response, "test2\ntest", status_code=200) def test_show_no_path(self): diff --git a/server/serverpl/filebrowser/views.py b/server/serverpl/filebrowser/views.py index 5fb6c15e84..95f188ac3b 100755 --- a/server/serverpl/filebrowser/views.py +++ b/server/serverpl/filebrowser/views.py @@ -363,8 +363,14 @@ def git_commit(request): commit = post.get('commit') if not commit: return HttpResponseBadRequest("Missing 'commit' parameter") - - ret, out, err = gitcmd.commit(join_fb_root(path), commit) + user = request.user + if not user.email: + return HttpResponseBadRequest("User must have an email") + if user.first_name and user.last_name: + name = user.get_full_name() + else: + name = user.get_username() + ret, out, err = gitcmd.commit(join_fb_root(path), commit, name=name, mail=request.user.email) if not ret: return HttpResponse(htmlprint.code(out + err)) else: # pragma: no cover