10

Sto pianificando di scrivere un piccolo compilatore di giocattoli in Haskell per un linguaggio molto semplice al fine di rafforzare le mie abilità Haskell e divertirmi a progettare una nuova lingua. Sto ancora pensando ad alcune decisioni generali e uno dei più grandi punti in sospeso è come farei i test di integrazione. Ho i seguenti requisiti:Test di integrazione del compilatore in Haskell

  • Dovrebbe essere possibile creare una lista di triple (ingresso, programma, uscita), in modo tale che il mio test di integrazione corre il mio compilatore per compilare il programma, viene eseguito il binario compilato, passa il input ad esso e verifica che l'output della corsa sia uguale all'output specificato.
    • Dovrebbe essere possibile estendere il test di integrazione in un secondo momento per testare interazioni più complesse con il programma, ad es. che cambia un file o dorme per almeno 20 secondi o qualcosa del genere.

Ho anche i seguenti requisiti facoltativi:

  • dovrebbe essere tanto un "end to end" possibile, vale a dire che dovrebbe trattare il wholencompiler come blackbox il più possibile e non dovrebbe avere accesso ai componenti interni del compilatore o qualcosa del genere.
  • Tutto il codice deve essere scritto in Haskell.
  • Sarebbe bello se ottenessi la tipica funzionalità di testing framework gratuitamente, cioè senza implementarla da solo. Per esempio. un messaggio verde "SUCCESSO" o una raccolta di messaggi di errore che descrivono i guasti.

Ho cercato di trovare qualcosa che soddisfi i miei bisogni, ma finora non ho avuto successo. Le alternative che ho considerato sono le seguenti:

  • shunit avrebbe soddisfatto tutto tranne la condizione che mi piacerebbe scrivere il codice in Haskell.
  • QuickCheck mi permetteva di scrivere tutto in Haskell, ma a quanto ho capito, sembra essere particolarmente adatto per test che riguardano solo una funzione Haskell e il suo risultato. Quindi dovrei testare le funzioni nel compilatore e rilassare il mio requisito "end-to-end".
  • Potrei semplicemente scrivere un programma Haskell che avvia il compilatore in un altro processo, lo passa il programma di input e quindi avvia il codice compilato in un altro processo, lo passa nell'intern e controlla l'output. Ciò richiederebbe comunque un sacco di codice da parte mia per implementare tutte le funzionalità che si ottengono gratuitamente quando si utilizza un framework di test.

Non sono ancora sicuro quale opzione dovrei scegliere e spero ancora che mi manchi una buona soluzione. Hai qualche idea su come potrei creare un test di integrazione che soddisfi tutti i miei requisiti?

risposta

4

sto pensando unsafePerformIO dovrebbe essere abbastanza sicuro qui considerando i programmi non dovrebbero mai interagire con tutto ciò che l'ambiente di test/logici dipende, o, in altre parole, la compilazione e l'esecuzione dovrebbe essere visibile come una funzione pura il contesto del test in un ambiente controllato e isolato. E immagino sia davvero utile testare il tuo compilatore in tali condizioni. E QuickCheck dovrebbe quindi diventare un'opzione anche per i test blackbox. Ma alcune menti più chiare potrebbero provare che mi sbaglio.

Quindi supponendo che il compilatore è smth come:

type Source = String 

compile :: Source -> Program 

e la tua esecuzione del programma è

data Report = Report Output TimeTaken OtherStats ... 

execute :: Program -> IO Report 

si potrebbe abbastanza tranquillamente usare unsafePerformIO convertire in

execute' :: Program -> Report -- or perhaps 'executeUnsafe' 
execute' = unsafePerformIO . execute 

e poi

compileAndExec :: Source -> Report 
compileAndExec = compile . execute' 

e utilizzare quello con QuickCheck.


Sia execute invoca un sottoprocesso, ottiene un vero e proprio binario, esegue quello, ecc, - o interpreta il binario (o bytecode) in memoria, sta a voi.

Ma mi piacerebbe separare il codice byte e la generazione binaria: in questo modo è possibile testare il compilatore separatamente dal linker/whatnot, che è più semplice, e ottenere anche un interprete nel processo.

+1

Ok, grazie mille, questa è in realtà una buona possibilità. Non ho ancora usato unsafePerformIO e quando ne ho sentito parlare, ho deciso di evitarlo il più possibile, ma immagino che questo sia esattamente uno dei casi in cui è utile. L'operazione è abbastanza pura, ma Haskell non lo sa. E per ottenere il framework QuickCheck per capirlo, devo usare questo. E inoltre, la mia paura di usare unsafePerformIO non dovrebbe applicarsi comunque al codice di test. – Lykos

+0

_ "ma Haskell non lo sa" _ - esattamente! In realtà ho persino pensato di aggiungere questa idea alla risposta! Lo stesso dicasi per 'unsafeCoerce' - è quando si _know_ per un fatto qualcosa è corretto ma Haskell ortodossa non sembra proprio adattarsi al fatto nel suo sistema di credenze. –