LukeSavefrogs / danea-easyfatt

Danea Easyfatt automation suite
https://lukesavefrogs.github.io/danea-easyfatt/
GNU General Public License v3.0
4 stars 0 forks source link

Crash silenzioso causato da `→` durante i test automatici #141

Closed LukeSavefrogs closed 11 months ago

LukeSavefrogs commented 11 months ago

Problema

Durante i test automatici (python -m unittest discover --catch) il test tests.test_bundle.test_error_file_not_found fallisce sempre in maniera silenziosa, in quanto non viene mai stampato The following required files were not found.

Questo testo viene da un'eccezione rilasciata dalla funzione require_files:

https://github.com/LukeSavefrogs/danea-easyfatt/blob/8d8c0630153a276dd7195ff42cf98ff5f5c1b147/src/veryeasyfatt/app/main.py#L50-L52

Aggiungendo poco prima della Exception una print con la stessa stringa otteniamo l'errore di seguito:

[30-10-2023 15:41:39] ERROR    Eccezione inaspettata nell'applicazione
Traceback (most recent call last):
  File "bootstrap.py", line 197, in main
  File "app\main.py", line 91, in main
  File "app\main.py", line 51, in require_files
  File "encodings\cp1252.py", line 19, in encode
UnicodeEncodeError: 'charmap' codec can't encode character '\u2192' in position 47: character 
maps to <undefined>
Premi [INVIO] per terminare il programma...

Sembra quindi che il carattere crei problemi durante i test automatici (con o senza il flag --disable-rich-logging).

Analisi

Prendendo spunto da questa risposta su SO, ho controllato il tipo di encoding di default utilizzato:

import sys, locale, os
print(sys.stdout.encoding)
print(sys.stdout.isatty())
print(locale.getpreferredencoding())
print(sys.getfilesystemencoding())
print(os.environ.get("PYTHONIOENCODING", None))
print("→")
print(chr(246), chr(9786), chr(9787))
sys.exit()

Questi sono i risultati del test:

Sembra che quindi la differenza stia nel sys.stdout.encoding, che nel caso di una console NON interattiva (sys.stdout.isatty() infatti restituisce False) assume un default platform-specific (come visto nell'issue #107 il character encoding per Windows storicamente è cp1252, cioè Windows-1252).

Soluzioni

Purtroppo, in entrambe le soluzioni trovate i caratteri non vengono mostrati correttamente ( diventa →)...

io.TextIOWrapper

Il seguente è più un workaround che una soluzione vera e propria:

# src/veryeasyfatt/bootstrap.py

import io
import sys

# ...

sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding="utf-8")
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")

# ...

Il risultato è questo:

Poe => python -m unittest discover --catch
+utf-8     
-False     
cp1252    
utf-8     
None      
→       
ö ☺ ☻

ATTENZIONE:

E' mandatorio che sys.stdout venga sovrascritto PRIMA che esso venga usato in qualche modo, o che il logger venga istanziato (in special modo lo StreamHandler), altrimenti in fase di logging resterà il puntamento al "vecchio" sys.stdout.

Forzatura PYTHONUTF8 durante build

E' possibile specificare il valore della variabile PYTHONUTF8 direttamente in fase di build dell'eseguibile.

L'equivalente CLI di quanto mostrato nell'esempio è --python-option PYTHON_OPTION.

Tuttavia facendo ciò il risultato è il seguente:

Poe => python -m unittest discover --catch
+utf-8
-False
UTF-8
utf-8
None
→
ö ☺ ☻
LukeSavefrogs commented 11 months ago

Piccola nota per chiarire un punto non chiaro all'inizio:

Non viene stampata l'exception UnicodeEncodeError perchè nei test stdout e stderr vengono catturati separatamente:

https://github.com/LukeSavefrogs/danea-easyfatt/blob/baedb617c6383f05c8805b6d8c1b22b7487b3e03/tests/test_bundle.py#L101-L115

LukeSavefrogs commented 11 months ago

Analisi soluzioni disponibili

Entrambe le soluzioni funzionavano.

Tuttavia per comodità al momento preferisco impostare il flag -X utf8 all'interprete python in modo da ignorare i default di piattaforma. Ho scartato il metodo che utilizzava io.TextIOWrapper in quanto andava utilizzato prima di qualsiasi altro utilizzo di sys.stdout o sys.stdin, altrimenti avrebbe potuto non funzionare per gli oggetti che utilizzano il "vecchio" stdout.

Soluzione

Il problema può essere risolto in 2 step:

  1. Aggiunta del flag --python-option X utf8 al comando di build dell'eseguibile
  2. Usare subprocess.run(..., encoding="utf8") (notare come venga specificato esplicitamente il tipo di encoding da utilizzare) ogni volta che nei test va lanciato l'eseguibile.

Il comando con cui viene attualmente "compilato" l'eseguibile:

https://github.com/LukeSavefrogs/danea-easyfatt/blob/baedb617c6383f05c8805b6d8c1b22b7487b3e03/scripts/build.py#L55-L66