drivaspreset opened a new pull request, #43607:
URL: https://github.com/apache/superset/pull/43607

   ### SUMMARY
   
   Closes the unit-test gaps in `PrestoEngineSpec`. **No production code 
changes** — this is tests
   only
   
   Most of this file's behaviour was either proven only in the integration 
suite (which needs an app
   and a metadata DB) or not proven at all. Several methods were completely 
uncovered: `handle_cursor`,
   `get_tracking_url`, `get_function_names`, `get_allow_cost_estimate`, 
`get_catalog_names`, and the
   `PrestoBaseEngineSpec.convert_dttm` override. That matters because the logic 
here is pure and
   deterministic — reproducing its failures end-to-end needs a live Presto 
cluster, but pinning it down
   needs only mocks.
   
   `superset/db_engine_specs/presto.py`: **306 → 161 missed statements (34% → 
63%)**, and
   **+86 test cases** (`tests/unit_tests/db_engine_specs/test_presto.py` 47 → 
131,
   `tests/unit_tests/models/core_test.py` 77 → 79).
   
   The commits are grouped by behaviour area and are readable in order:
   
   | Commit | Area | Cases |
   |---|---|---:|
   | 1 | error regexes and `_extract_error_message` | 19 |
   | 2 | `convert_dttm` precision and `column_type_mappings` | 17 |
   | 3 | `handle_cursor` and `get_tracking_url` | 9 |
   | 4 | cost estimation | 13 |
   | 5 | view, table and catalog discovery | 18 |
   | 6 | `get_function_names` | 4 |
   | 7 | `expand_data` | 4 |
   | 8 | `Database.function_names`, null cost estimate | 3 |
   
   #### Highlights
   
   **All nine `custom_errors` regexes are now asserted.** Previously only the 
HTTP 401 pattern had a
   test, so a typo in any of the other eight shipped silently — invisible until 
a user hit that exact
   error. This is the most directly user-facing logic in the file: the 
difference between
   *"Either the username "bob" or the password is incorrect"* and a raw pyhive 
string in a red toast.
   
   **The Stop button.** `handle_cursor` is what notices a user clicked Stop — 
the click only updates the
   query's status in the metadata database, and the `cursor.cancel()` this loop 
issues is what actually
   releases the Presto cluster resources. That branch had no test; had it 
regressed, Stop would appear
   to work in the UI while the query kept running.
   
   **The millisecond/microsecond override.** `PrestoEngineSpec.convert_dttm` 
deliberately overrides
   `PrestoBaseEngineSpec.convert_dttm` to render TIMESTAMP literals with 
millisecond rather than
   microsecond precision, and nothing pinned that. It is also this file's only 
silent-failure mode: a
   wrong-precision literal produces a *valid* query returning the wrong rows, 
with no error. A refactor
   collapsing the two methods now fails a test instead of quietly shifting 
every Presto timestamp
   filter.
   
   **Schema round-trip.** `adjust_engine_params` percent-quotes the schema into 
the URI path with
   `safe=""` and `get_schema_from_engine_params` unquotes it. Any asymmetry 
between the pair silently
   points SQL Lab at the wrong schema, and a schema containing `/` would be 
read back as a different
   name entirely. Tested as a round-trip over `/`, spaces, `%` and non-ASCII, 
since testing either half
   alone cannot catch a mismatched pair.
   
   #### Mirrored integration tests
   
   Twelve cases mirror tests that already exist in
   `tests/integration_tests/db_engine_specs/presto_tests.py`, reusing that 
suite's known-good
   expectations. **The integration originals are intentionally left in place — 
that file is untouched by
   this PR** (`git diff --stat` shows no changes to it). Each mirror's 
docstring names its original by
   file and line, so the relationship is greppable rather than accidental. 
Removing the now-duplicated
   integration tests, if ever wanted, is separate work.
   
   #### Characterization tests — 8 tests that assert current, incorrect 
behaviour
   
   These pass by pinning bugs, not by endorsing them. Each is labelled 
`CHARACTERIZATION test` in its
   docstring and explains what a fix would change. No `xfail` anywhere — that 
would silently mask the
   gap. Happy to split any of these out or drop them if maintainers would 
rather they land alongside
   their fixes:
   
   1. **`extract_errors` raises `KeyError` when `context` is omitted.** Four of 
the nine patterns
      declare message placeholders their own regex never captures (`username`, 
`hostname`, `port`), so
      the eager-`gettext` `str % dict` raises. Reachable from real call sites.
   2. **`handle_cursor` raises `TypeError` on missing split counts.** 
`completedSplits`/`totalSplits`
      are read with `stats.get(...)` and passed straight to `float()`, so a 
poll reporting a state
      without split counts raises inside the SQL Lab worker rather than 
degrading to "no progress
      information".
   3. **`get_allow_cost_estimate` raises `InvalidVersion` on a malformed 
`version`.** An admin typo in
      a database's Extra JSON raises rather than simply disabling cost 
estimation.
   4. **`query_cost_formatter` raises `TypeError` on a null estimate value.** 
`humanize` guards
      `int(value)` with `except ValueError` only, but a JSON `null` arrives as 
`None`. One null field
      breaks the whole cost panel — note that an *absent* key is handled 
cleanly, so the asymmetry is
      the bug.
   5. **`expand_data` crashes on malformed JSON — two unguarded sites, one test 
each.** `destringify`
      is `json.loads` with no `try/except` (`superset/result_set.py`), reached 
from both the ARRAY
      branch and the ROW branch. Behind `PRESTO_EXPAND_DATA`, which defaults 
off. Both sites are
      pinned, since a fix guarding only one would leave the other crashing.
   6. **`get_function_names` raises `KeyError` on a DataFrame without a 
`Function` column.** Included
      because "handles an empty function list" has two readings that behave 
differently; both are now
      asserted explicitly.
   7. **The "table does not exist" message double-quotes the table name.**
      `TABLE_DOES_NOT_EXIST_REGEX` captures the surrounding single quotes, so 
users see
      `The table "'default.foo'" does not exist.` Cosmetic, and changing 
user-facing copy is a product
      decision.
   
   #### Two other findings, not addressed here
   
   - 
`tests/integration_tests/db_engine_specs/presto_tests.py::test_get_catalog_names`
 returns early
     unless the example database is Presto, and asserts against a **list** 
while `get_catalog_names`
     returns a **set** — it would fail if it ever ran. Left alone deliberately 
(that file is untouched);
     the unit test added here is the method's first real coverage.
   - `get_create_view` interpolates `schema` and `table` into `SHOW CREATE 
VIEW` with an f-string rather
     than binding them. Those values come from the metadata database rather 
than user input, so this is
     not a security finding under `SECURITY.md`; one test case pins the 
interpolation so the assumption
     is visible if that ever changes.
   
   Happy to open GitHub issues for any of the above that maintainers consider 
real — I did not want to
   file eight issues speculatively.
   
   ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
   
   No UI changes. The meaningful before/after is coverage of 
`superset/db_engine_specs/presto.py`:
   
   ```
   BEFORE   508 statements   306 missed   180 branches   13 partial   34%
   AFTER    508 statements   161 missed   180 branches   22 partial   63%
   ```
   
   Methods that went from **entirely uncovered** to covered:
   
   | Lines | Method |
   |---|---|
   | 308-315 | `PrestoBaseEngineSpec.convert_dttm` (microseconds) |
   | 340-341 | `get_catalog_names` |
   | 402-419 | `estimate_statement_cost` |
   | 431-462 | `query_cost_formatter` |
   | 1029-1030 | `get_allow_cost_estimate` |
   | 1077 | `get_table_names` |
   | 1105-1127 | `get_view_names` |
   | 1235-1319 | `expand_data` |
   | 1374-1385 | `get_create_view` |
   | 1389-1393 | `get_tracking_url` |
   | 1398-1442 | `handle_cursor` |
   | 1451-1460 | `_extract_error_message` |
   
   One caveat worth stating: **line coverage understates the change for the 
error regexes.** The loop in
   `BaseEngineSpec.extract_errors` already read as covered after a single 
pattern matched, while 8 of 9
   patterns had no assertion on their output at all. Read the case counts 
alongside the percentages.
   
   ### TESTING INSTRUCTIONS
   
   ```bash
   pytest tests/unit_tests/db_engine_specs/test_presto.py 
tests/unit_tests/models/core_test.py
   ```
   
   210 tests, ~1.3s. No app, no database, no Presto cluster, no network — all 
mocked.
   
   Coverage, for the numbers above:
   
   ```bash
   python -m coverage run -m pytest 
tests/unit_tests/db_engine_specs/test_presto.py
   python -m coverage report --include="*/db_engine_specs/presto.py" 
--show-missing
   ```
   
   To confirm the integration suite is untouched:
   
   ```bash
   git diff --stat master..HEAD -- tests/integration_tests/   # expect no output
   ```
   
   ### ADDITIONAL INFORMATION
   
   - [ ] Has associated issue:
   - [ ] Required feature flags:
   - [ ] Changes UI
   - [ ] Includes DB Migration (follow approval process in 
[SIP-59](https://github.com/apache/superset/issues/13351))
     - [ ] Migration is atomic, supports rollback & is backwards-compatible
     - [ ] Confirm DB migration upgrade and downgrade tested
     - [ ] Runtime estimates and downtime expectations provided
   - [ ] Introduces new feature or API
   - [ ] Removes existing feature or API
   
   None of the above applies: tests only
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to