vancauwe commited on
Commit
8209004
·
unverified ·
2 Parent(s): 821ac40 836bd51

Merge pull request #38 from sdsc-ordes/fix/spoof-metadata

Browse files
.github/workflows/python-pytest.yml CHANGED
@@ -34,3 +34,4 @@ jobs:
34
  - name: Run quick tests with pytest
35
  run: |
36
  pytest -m "not slow and not visual" --strict-markers --ignore=tests/visual_selenium
 
 
34
  - name: Run quick tests with pytest
35
  run: |
36
  pytest -m "not slow and not visual" --strict-markers --ignore=tests/visual_selenium
37
+
.github/workflows/python-visualtests.yml CHANGED
@@ -51,3 +51,5 @@ jobs:
51
  # otherwise, not one step it consistently fails at.)
52
  run: |
53
  pytest -m "visual" --strict-markers tests/visual_selenium/ -s --demo
 
 
 
51
  # otherwise, not one step it consistently fails at.)
52
  run: |
53
  pytest -m "visual" --strict-markers tests/visual_selenium/ -s --demo
54
+
55
+ # DEBUG_AUTOPOPULATE_METADATA=True streamlit run src/main.py
docs/dev_notes.md CHANGED
@@ -13,7 +13,7 @@ Then use a web browser to view the site indiciated, by default: http://localhost
13
 
14
  # How to build and view docs locally
15
 
16
- We have a CI action to presesnt the docs on github.io.
17
  To validate locally, you need the deps listed in `requirements.txt` installed.
18
 
19
  Run
@@ -51,14 +51,15 @@ The CI runs with `--strict-markers` so any new marker must be registered in
51
 
52
  - the basic CI action runs the fast tests only, skipping all tests marked
53
  `visual` and `slow`
54
- - the CI action on PR runs the `slow` tests, but stil excluding `visual`.
55
- - TODO: a new action for the visual tests is to be developed.
56
 
57
  Check all tests are marked ok, and that they are filtered correctly by the
58
  groupings used in CI:
59
  ```bash
60
  pytest --collect-only -m "not slow and not visual" --strict-markers --ignore=tests/visual_selenium
61
  pytest --collect-only -m "not visual" --strict-markers --ignore=tests/visual_selenium
 
62
  ```
63
 
64
 
@@ -97,7 +98,8 @@ pytest --cov-report=lcov --cov=src
97
 
98
  We use seleniumbase to test the visual appearance of the app, including the
99
  presence of elements that appear through the workflow. This testing takes quite
100
- a long time to execute and is not yet configured with CI.
 
101
 
102
  ```bash
103
  # install packages for app and for visual testing
@@ -106,14 +108,15 @@ pip install -r tests/visual_selenium/requirements_visual.txt
106
  ```
107
 
108
  **Running tests**
109
- The execution of these tests requires that the site/app is running already.
 
110
 
111
- In one tab:
112
  ```bash
113
  streamlit run src/main.py
114
  ```
115
 
116
- In another tab:
117
  ```bash
118
  # run just the visual tests
119
  pytest -m "visual" --strict-markers
@@ -132,3 +135,17 @@ pytest -m "not slow and not visual" --strict-markers --ignore=tests/visual_selen
132
  Initially we have an action setup that runs all tests in the `tests` directory, within the `test/tests` branch.
133
 
134
  TODO: Add some test report & coverage badges to the README.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  # How to build and view docs locally
15
 
16
+ We have a CI action to present the docs on github.io.
17
  To validate locally, you need the deps listed in `requirements.txt` installed.
18
 
19
  Run
 
51
 
52
  - the basic CI action runs the fast tests only, skipping all tests marked
53
  `visual` and `slow`
54
+ - the CI action on PR runs the `slow` tests, but still excluding `visual`.
55
+ - a second action for the visual tests runs on PR.
56
 
57
  Check all tests are marked ok, and that they are filtered correctly by the
58
  groupings used in CI:
59
  ```bash
60
  pytest --collect-only -m "not slow and not visual" --strict-markers --ignore=tests/visual_selenium
61
  pytest --collect-only -m "not visual" --strict-markers --ignore=tests/visual_selenium
62
+ pytest --collect-only -m "visual" --strict-markers tests/visual_selenium/ -s --demo
63
  ```
64
 
65
 
 
98
 
99
  We use seleniumbase to test the visual appearance of the app, including the
100
  presence of elements that appear through the workflow. This testing takes quite
101
+ a long time to execute. It is configured in a separate CI action
102
+ (`python-visualtests.yml`).
103
 
104
  ```bash
105
  # install packages for app and for visual testing
 
108
  ```
109
 
110
  **Running tests**
111
+ The execution of these tests requires that the site/app is running already, which
112
+ is handled by a fixture (that starts the app in another thread).
113
 
114
+ Alternatively, in one tab, run:
115
  ```bash
116
  streamlit run src/main.py
117
  ```
118
 
119
+ In another tab, run:
120
  ```bash
121
  # run just the visual tests
122
  pytest -m "visual" --strict-markers
 
135
  Initially we have an action setup that runs all tests in the `tests` directory, within the `test/tests` branch.
136
 
137
  TODO: Add some test report & coverage badges to the README.
138
+
139
+
140
+ ## Environment flags used in development
141
+
142
+ - `DEBUG_AUTOPOPULATE_METADATA=True` : Set this env variable to have the text
143
+ inputs autopopulated, to make stepping through the workflow faster during
144
+ development work.
145
+
146
+ Typical usage:
147
+
148
+ ```bash
149
+ DEBUG_AUTOPOPULATE_METADATA=True streamlit run src/main.py
150
+ ```
151
+
src/input/input_handling.py CHANGED
@@ -2,6 +2,7 @@ from typing import List, Tuple
2
  import datetime
3
  import logging
4
  import hashlib
 
5
 
6
  import streamlit as st
7
  from streamlit.delta_generator import DeltaGenerator
@@ -23,15 +24,31 @@ both the UI elements (setup_input_UI) and the validation functions.
23
  '''
24
  allowed_image_types = ['jpg', 'jpeg', 'png', 'webp']
25
 
 
 
 
 
 
 
 
 
 
26
  # an arbitrary set of defaults so testing is less painful...
27
  # ideally we add in some randomization to the defaults
28
- spoof_metadata = {
29
- "latitude": 0.5,
30
- "longitude": 44,
31
- "author_email": "super@whale.org",
32
- "date": None,
33
- "time": None,
34
- }
 
 
 
 
 
 
 
35
 
36
  def check_inputs_are_set(empty_ok:bool=False, debug:bool=False) -> bool:
37
  """
@@ -50,12 +67,15 @@ def check_inputs_are_set(empty_ok:bool=False, debug:bool=False) -> bool:
50
  return empty_ok
51
 
52
  exp_input_key_stubs = ["input_latitude", "input_longitude", "input_date", "input_time"]
53
- #exp_input_key_stubs = ["input_latitude", "input_longitude", "input_author_email", "input_date", "input_time",
54
 
55
  vals = []
56
  # the author_email is global/one-off - no hash extension.
57
  if "input_author_email" in st.session_state:
58
  val = st.session_state["input_author_email"]
 
 
 
 
59
  vals.append(val)
60
  if debug:
61
  msg = f"{'input_author_email':15}, {(val is not None):8}, {val}"
@@ -190,10 +210,11 @@ def metadata_inputs_one_file(file:UploadedFile, image_hash:str, dbg_ix:int=0) ->
190
  msg = f"[D] {filename}: lat, lon from image metadata: {latitude0}, {longitude0}"
191
  m_logger.debug(msg)
192
 
193
- if latitude0 is None: # get some default values if not found in exifdata
194
- latitude0:float = spoof_metadata.get('latitude', 0) + dbg_ix
195
- if longitude0 is None:
196
- longitude0:float = spoof_metadata.get('longitude', 0) - dbg_ix
 
197
 
198
  image = st.session_state.images.get(image_hash, None)
199
  # add the UI elements
 
2
  import datetime
3
  import logging
4
  import hashlib
5
+ import os
6
 
7
  import streamlit as st
8
  from streamlit.delta_generator import DeltaGenerator
 
24
  '''
25
  allowed_image_types = ['jpg', 'jpeg', 'png', 'webp']
26
 
27
+ def _is_str_true(v:str) -> bool:
28
+ ''' convert a string to boolean: if contains True or 1 (or yes), return True '''
29
+ # https://stackoverflow.com/questions/715417/converting-from-a-string-to-boolean-in-python
30
+ return v.lower() in ("yes", "true", "t", "1")
31
+
32
+ def load_debug_autopopulate() -> bool:
33
+ return _is_str_true( os.getenv("DEBUG_AUTOPOPULATE_METADATA", "False"))
34
+
35
+
36
  # an arbitrary set of defaults so testing is less painful...
37
  # ideally we add in some randomization to the defaults
38
+ dbg_populate_metadata = load_debug_autopopulate()
39
+
40
+ # the other main option would be argparse, where we can run `streamlit run src/main.py -- --debug` or similar
41
+ # - I think env vars are simple and clean enough, it isn't really a CLI that we want to offer debug options, it is for dev.
42
+ if dbg_populate_metadata:
43
+ spoof_metadata = {
44
+ "latitude": 0.5,
45
+ "longitude": 44,
46
+ "author_email": "[email protected]",
47
+ "date": None,
48
+ "time": None,
49
+ }
50
+ else:
51
+ spoof_metadata = {}
52
 
53
  def check_inputs_are_set(empty_ok:bool=False, debug:bool=False) -> bool:
54
  """
 
67
  return empty_ok
68
 
69
  exp_input_key_stubs = ["input_latitude", "input_longitude", "input_date", "input_time"]
 
70
 
71
  vals = []
72
  # the author_email is global/one-off - no hash extension.
73
  if "input_author_email" in st.session_state:
74
  val = st.session_state["input_author_email"]
75
+ # if val is a string and empty, set to None
76
+ if isinstance(val, str) and not val:
77
+ val = None
78
+
79
  vals.append(val)
80
  if debug:
81
  msg = f"{'input_author_email':15}, {(val is not None):8}, {val}"
 
210
  msg = f"[D] {filename}: lat, lon from image metadata: {latitude0}, {longitude0}"
211
  m_logger.debug(msg)
212
 
213
+ if spoof_metadata:
214
+ if latitude0 is None: # get some default values if not found in exifdata
215
+ latitude0:float = spoof_metadata.get('latitude', 0) + dbg_ix
216
+ if longitude0 is None:
217
+ longitude0:float = spoof_metadata.get('longitude', 0) - dbg_ix
218
 
219
  image = st.session_state.images.get(image_hash, None)
220
  # add the UI elements
tests/test_demo_input_sidebar.py CHANGED
@@ -3,6 +3,7 @@ from pathlib import Path
3
  from io import BytesIO
4
  from PIL import Image
5
  import numpy as np
 
6
 
7
  import pytest
8
  from unittest.mock import MagicMock, patch
@@ -12,7 +13,7 @@ import time
12
 
13
  from input.input_handling import spoof_metadata
14
  from input.input_observation import InputObservation
15
- from input.input_handling import buffer_uploaded_files
16
 
17
  from streamlit.runtime.uploaded_file_manager import UploadedFile
18
 
@@ -184,7 +185,13 @@ def test_no_input_no_interaction():
184
  at = AppTest.from_file(SCRIPT_UNDER_TEST, default_timeout=10).run()
185
  verify_initial_session_state(at)
186
 
187
- assert at.session_state.input_author_email == spoof_metadata.get("author_email")
 
 
 
 
 
 
188
 
189
  # print (f"[I] whole tree: {at._tree}")
190
  # for elem in at.sidebar.markdown:
 
3
  from io import BytesIO
4
  from PIL import Image
5
  import numpy as np
6
+ import os
7
 
8
  import pytest
9
  from unittest.mock import MagicMock, patch
 
13
 
14
  from input.input_handling import spoof_metadata
15
  from input.input_observation import InputObservation
16
+ from input.input_handling import buffer_uploaded_files, load_debug_autopopulate
17
 
18
  from streamlit.runtime.uploaded_file_manager import UploadedFile
19
 
 
185
  at = AppTest.from_file(SCRIPT_UNDER_TEST, default_timeout=10).run()
186
  verify_initial_session_state(at)
187
 
188
+ dbg = load_debug_autopopulate()
189
+ #var = at.session_state.input_author_email
190
+ #_cprint(f"[I] input email is '{var}' type: {type(var)} | is None? {var is None} | {dbg}", PURPLE)
191
+ if dbg: # autopopulated
192
+ assert at.session_state.input_author_email == spoof_metadata.get("author_email")
193
+ else: # should be empty, the user has to fill it in
194
+ assert at.session_state.input_author_email == ""
195
 
196
  # print (f"[I] whole tree: {at._tree}")
197
  # for elem in at.sidebar.markdown:
tests/test_demo_multifile_upload.py CHANGED
@@ -26,7 +26,7 @@ from streamlit.testing.v1 import AppTest
26
 
27
 
28
  # for expectations
29
- from input.input_handling import spoof_metadata
30
  from input.input_validator import get_image_datetime, get_image_latlon
31
 
32
 
@@ -137,7 +137,11 @@ def test_no_input_no_interaction():
137
 
138
  at = AppTest.from_file("src/apptest/demo_multifile_upload.py").run()
139
  assert at.session_state.observations == {}
140
- assert at.session_state.input_author_email == spoof_metadata.get("author_email")
 
 
 
 
141
 
142
  def test_bad_email():
143
  with patch.dict(spoof_metadata, {"author_email": "notanemail"}):
 
26
 
27
 
28
  # for expectations
29
+ from input.input_handling import spoof_metadata, load_debug_autopopulate
30
  from input.input_validator import get_image_datetime, get_image_latlon
31
 
32
 
 
137
 
138
  at = AppTest.from_file("src/apptest/demo_multifile_upload.py").run()
139
  assert at.session_state.observations == {}
140
+ dbg = load_debug_autopopulate()
141
+ if dbg: # autopopulated
142
+ assert at.session_state.input_author_email == spoof_metadata.get("author_email")
143
+ else: # should be empty, the user has to fill it in
144
+ assert at.session_state.input_author_email == ""
145
 
146
  def test_bad_email():
147
  with patch.dict(spoof_metadata, {"author_email": "notanemail"}):
tests/test_main.py CHANGED
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch
3
  from streamlit.testing.v1 import AppTest
4
  import time
5
 
6
- from input.input_handling import spoof_metadata
7
  from input.input_observation import InputObservation
8
  from input.input_handling import buffer_uploaded_files
9
 
@@ -72,7 +72,10 @@ def test_click_validate_after_data_entry(mock_file_rv: MagicMock, mock_uploadedF
72
  assert infer_button.disabled == True
73
 
74
 
75
- # 2. upload files, and trigger the callback
 
 
 
76
 
77
  # put the mocked file_upload into session state, as if it were the result of a file upload, with the key 'file_uploader_data'
78
  at.session_state["file_uploader_data"] = mock_files
 
3
  from streamlit.testing.v1 import AppTest
4
  import time
5
 
6
+ from input.input_handling import spoof_metadata, load_debug_autopopulate
7
  from input.input_observation import InputObservation
8
  from input.input_handling import buffer_uploaded_files
9
 
 
72
  assert infer_button.disabled == True
73
 
74
 
75
+ # 2. upload files, enter email, and trigger the callback
76
+ if not load_debug_autopopulate():
77
+ # fill the text box with a dummy email
78
+ at.session_state.input_author_email = "[email protected]"
79
 
80
  # put the mocked file_upload into session state, as if it were the result of a file upload, with the key 'file_uploader_data'
81
  at.session_state["file_uploader_data"] = mock_files
tests/visual_selenium/test_visual_main.py CHANGED
@@ -208,6 +208,7 @@ class RecorderTest(BaseCase):
208
  # - setup steps:
209
  # - open the app
210
  # - upload two images
 
211
  # - validate the data entry
212
  # - click the infer button, wait for ML
213
  # - the real test steps:
@@ -228,6 +229,8 @@ class RecorderTest(BaseCase):
228
  'input[data-testid="stFileUploaderDropzoneInput"]',
229
  "\n".join([str(img_f1), str(img_f2)]),
230
  )
 
 
231
 
232
  # advance to the next step, by clicking the validate button (wait for it first)
233
  wait_for_element(self, By.XPATH, "//button//strong[contains(text(), 'Validate')]")
 
208
  # - setup steps:
209
  # - open the app
210
  # - upload two images
211
+ # - enter author email
212
  # - validate the data entry
213
  # - click the infer button, wait for ML
214
  # - the real test steps:
 
229
  'input[data-testid="stFileUploaderDropzoneInput"]',
230
  "\n".join([str(img_f1), str(img_f2)]),
231
  )
232
+ # enter author email
233
+ self.type('input[aria-label="Author Email"]', "[email protected]\n")
234
 
235
  # advance to the next step, by clicking the validate button (wait for it first)
236
  wait_for_element(self, By.XPATH, "//button//strong[contains(text(), 'Validate')]")