diff --git a/.gitignore b/.gitignore index 46e5469eeaa0cdc2749a696de34e248d8efb6aa5..179e5e16ed9770732dc73934dddc8571a25f7bd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# ignore html directory that contains output of pdoc +html + +# gitlab pages directory +public + # virtual environment folder venv diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cd37ccfd0697a141ad8a9674e7cd361f82a763ce..a2ebe19ff16ae52b8d1ed92ae766b15b9695c13e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,41 +5,36 @@ stages: job_lint_flake8: stage: lint - image: morkolai/paa-bittet-ci + image: morkolai/soitool-ci script: - flake8 --version - - flake8 soitool + - flake8 --config scripts/.flake8 soitool test job_lint_pylint: stage: lint - image: morkolai/paa-bittet-ci + image: morkolai/soitool-ci script: - pylint --version - - pip install pyside2 - - pylint --rcfile=scripts/.pylintrc soitool + - pylint --rcfile=scripts/.pylintrc soitool test job_lint_bandit: stage: lint - image: morkolai/paa-bittet-ci + image: morkolai/soitool-ci script: - bandit --version - - bandit -r soitool + - bandit -r soitool test job_lint_pydocstyle: stage: lint - image: morkolai/paa-bittet-ci + image: morkolai/soitool-ci script: - - pip install pydocstyle - pydocstyle --version - - pydocstyle --convention=numpy soitool + - pydocstyle --match '.*.py' --convention=numpy soitool test job_test_gui_ubuntu_vnc: stage: test - image: morkolai/paa-bittet-ci - before_script: - - apt-get install -y libgl1-mesa-glx + image: morkolai/soitool-ci script: - - pip install pyside2 # -platform because running with a screen is not supported # https://stackoverflow.com/questions/17106315/failed-to-load-platform-plugin-xcb-while-launching-qt5-app-on-linux-without - QT_QPA_PLATFORM=vnc python3 -m unittest test.test_main @@ -52,21 +47,20 @@ job_test_gui_windows: - python --version - python -m unittest test.test_main -#job_test_gui_ubuntu: -# stage: test -# tags: -# - ci-ubuntu -# script: -# - python3 --version -# - DISPLAY=':10.0' python3 -m unittest test.test_main +job_test_gui_ubuntu: + stage: test + tags: + - ci-ubuntu + script: + - python3 --version + - DISPLAY=':10.0' python3 -m unittest test.test_main # name **må** være pages pages: stage: deploy - image: morkolai/paa-bittet-ci + image: morkolai/soitool-ci script: - mkdir public - - pip install pdoc3 pyside2 - pdoc --version - pdoc soitool --html - mv ./html/soitool/* ./public diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e1cd2b012ede6a78fc06c7a1fc7e2889368f5ce6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu:16.04 + +# This Dockerfile describes the container used in .gitlab-ci.yml + +# Need docker build arg, such as --build-arg FLAKE8_VERSION=3.0.4 +ARG FLAKE8_VERSION=3.7 + +# Installing python3.7 +RUN apt-get update +RUN apt-get install software-properties-common -y +RUN add-apt-repository ppa:deadsnakes/ppa -y +RUN apt-get update +RUN apt-get install python3.7 -y +RUN python3.7 -V +RUN apt-get install python3-pip -y +RUN pip3 install --upgrade pip + +# To make -platform offscreen,minimal,vnc work +RUN apt-get install -y libegl1-mesa-dev \ + libfontconfig1-dev \ + libsdl1.2-dev \ + libwayland-dev \ + libxkbcommon-dev + +WORKDIR /code +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt diff --git a/README.md b/README.md index 874486f50bd3b384696d0140027f4c9f74ce67fa..b7538b1afdab2a83a1990e9b1a4abfe14de44e56 100644 --- a/README.md +++ b/README.md @@ -51,4 +51,44 @@ Gjøres med pdoc3: * `pdoc3 --html --output-dir .\docs .\soitool\main.py` -* Uten kildekode: `pdoc3 --html --config show_source_code=False --output-dir .\docs\ .\soitool\main.py` \ No newline at end of file +* Uten kildekode: `pdoc3 --html --config show_source_code=False --output-dir .\docs\ .\soitool\main.py` + +## Om `Dockerfile` + +Docker image som brukes i `.gitlab-ci.yml` er bygget med filen `Dockerfile` og er lastet opp som `morkolai/soitool-ci`. Docker image inneholder alle avhengigheter til prosjektet. Følgende prosedyre brukes for å oppdatere image. Dette må gjøres når `requirements.txt` endrer seg. + +```bash +docker build -t morkolai/soitool-ci . +docker login +docker push morkolai/soitool-ci +``` + +## Arbeidsmetode - Bruk av `git` + +Arbeid skal ikke skje direkte på `master` branch. For hver oppgave en vil utføre skal en ny branch lages, og denne må senere merges inn ved hjelp av en "Merge Request". Gjennomgang av dette er lagt fram under: + +```bash +# ny branch +git branch <branch navn> +# hoppe til eksisterende branch +git checkout <branch navn> +# ..jobb med koden.. +git add <...> +git commit -m "..." +# push til gitlab +git push origin <branch navn> +``` + +Merging til master skal skje via Merge Requests i GitLab. + +### Om arbeid utføres på feil branch + +`git stash` kan brukes for å lagre endringer i et "stash". Deretter kan en hoppe til riktig branch med `git checkout <branch navn>`, og kjøre `git stash pop`. + +## Arbeidsmetode - Hvordan skrive tester + +Hver modul burde testes. I praksis vil dette si at hver fil under `soitool/` med navn `X.py` burde ha en tilsvarende fil under `test/` med navn `test_X.py`. + +Ved GUI-testing av modulære dialoger (dialoger som stopper eksekvering av hovedvindu) foretrekkes fremgangsmåten som demonstreres i `test\test_main.py`, hvor testfunksjoner kjøres med `singleShot`. Antall millisekunder testen skal vente før testfunksjoner kjøres kan stilles inn for tregere maskiner. + +*TODO* hvordan skrive GUI tester. diff --git a/requirements.txt b/requirements.txt index e7003db1ed313fd7ea05e86259946a579bafc7f9..c3395b573911f97cc9e70e8baee6e4eb101d7899 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/scripts/.flake8 b/scripts/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..b67c07910db8b368186b279e7ae62434899ff202 --- /dev/null +++ b/scripts/.flake8 @@ -0,0 +1,5 @@ +[flake8] +# ignorerer sjekk for lange linjer, ettersom dette gjøres i pylint +# vi vil tillate lange linjer som avsluttes med link, og dette er mulig i +# pylint, men såvidt vi vet ikke mulig i flake8 +ignore = E501 diff --git a/scripts/.pylintrc b/scripts/.pylintrc index 7e9ef2c59e6d2485745e4bb9538672918024a7cb..43b2b9bbb126b33d46a4ce3faab76ffb6c1b2b72 100644 --- a/scripts/.pylintrc +++ b/scripts/.pylintrc @@ -275,7 +275,7 @@ redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )?<?https?://\S+>?$ +ignore-long-lines=^.*<?https?://\S+>?$ # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 diff --git a/test/__init__.py b/test/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5f1ae119d6f959cc9e9b1a127e305d5145dc620e 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -0,0 +1 @@ +"""Tester koden vår.""" diff --git a/test/test_main.py b/test/test_main.py index 8f422d611bdb75722b1adefa7388d81cef5e74b4..beadccfe5991936ceba67b155d69db315ddf1414 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -1,7 +1,10 @@ +"""Test CoolWidget.""" + import unittest import sys +from datetime import datetime as datetime_, timedelta +from PySide2 import QtWidgets, QtTest, QtCore from soitool import main -from PySide2 import QtGui, QtWidgets, QtTest, QtCore # references: # * findChild: https://srinikom.github.io/pyside-docs/PySide/QtCore/QObject.html#PySide.QtCore.PySide.QtCore.QObject.findChild @@ -18,26 +21,42 @@ from PySide2 import QtGui, QtWidgets, QtTest, QtCore # moved here from setUp to avoid annoying startup messages app = QtWidgets.QApplication(sys.argv) + def wait(msec): - QtTest.QTest.qWait(msec) + """Venter msec millisekunder. + + Bruker https://stackoverflow.com/a/34745326/3545896 + + Parameter + ----- + msec : number + msec to wait + """ + end = datetime_.now() + timedelta(milliseconds=msec) + while datetime_.now() < end: + app.processEvents() + class TestMain(unittest.TestCase): + """TestCase for main.""" def setUp(self): + """Forbereder widget for testing.""" self.test_text1 = "A bad boy" self.test_text2 = "Hei Anders!" self.widget = main.CoolWidget(self.test_text1) self.widget.show() def test_starts_up(self): + """Test at widget kan starte opp.""" self.assertEqual( - self.widget.qlabel.text(), - self.test_text1, + self.widget.qlabel.text(), + self.test_text1, ) self.assertTrue(self.widget.isVisible()) - - # This test shows how waiting for a referenced dialog can create more reliable tests. The only downside is having to ensure that the mouseClick after the singleShot is allowed to run. If we put a singleShot delay too short the line is never allowed to execute, and the while-loop of the inner function takes over the whole world + def test_change_text_ok(self): + """Test at endring av tekst funker.""" def change_text_and_ok(): while self.widget.dlg_input is None: @@ -46,7 +65,6 @@ class TestMain(unittest.TestCase): QtTest.QTest.keyClicks(child_line_edit, self.test_text2) QtTest.QTest.keyClick(child_line_edit, QtCore.Qt.Key_Enter) - # delay of 100 to allow the next line to execute QtCore.QTimer.singleShot(100, change_text_and_ok) QtTest.QTest.mouseClick(self.widget.button, QtCore.Qt.LeftButton) @@ -54,15 +72,17 @@ class TestMain(unittest.TestCase): self.widget.qlabel.text(), self.test_text2, ) - - # This test shows how we can use singleShot to trick code to be ran after .exec() of a dialog. The downside is that for slow computers the singleShot could be ran before .exec(). See the above test for an example approach to avoid this. A boon of this method is that we don't need to test against stored references to dialogs, we can instead rely on app.activeModalWidget() + def test_change_text_not_ok(self): + """Test at avbrytelse av endring av tekster ikke endrer tekst.""" def accept_popup(): QtTest.QTest.keyClick(app.activeModalWidget(), QtCore.Qt.Key_Enter) def change_text_and_not_ok(): - # in PySide2 we need to store a reference to this. If we don't the widget is garbage collected somehow before we get to use child_line_edit (a child of the active widget) + # in PySide2 we need to store a reference to this. If we don't the + # widget is garbage collected somehow before we get to use + # child_line_edit (a child of the active widget) active_widget = app.activeModalWidget() child_line_edit = active_widget.findChild(QtWidgets.QLineEdit) @@ -73,11 +93,12 @@ class TestMain(unittest.TestCase): QtCore.QTimer.singleShot(0, change_text_and_not_ok) QtTest.QTest.mouseClick(self.widget.button, QtCore.Qt.LeftButton) - + self.assertNotEqual( self.widget.qlabel.text(), self.test_text2, ) + if __name__ == '__main__': unittest.main()