# Multiprocessing Stress Tester (readme) Multiprocessing Stress Tester è uno strumento, realizzato principalmente in Python, che permette di controllare la correttezza di un programma C++ confrontandone gli output con quelli prodotti da un altro programma C++ ritenuto corretto, su molti input diversi. Questo tool diventa particolarmente utile per verificare la correttezza di un codice con tempi di esecuzione molto ridotti ma contenente eventuali errori confrontandone i risultati con quelli prodotti da una soluzione più lenta ma sicuramente corretta. I risultati ottenuti dall'utilizzo del tester sono salvati all'interno di un database (condiviso tra tutte le istanze del pacchetto) e possono essere visualizzati tramite lo stesso portale web tramite il quale viene utilizzato il tester stesso. ### Input Il tester prende come input: ```correct.cpp```: il sorgente ritenuto corretto, ```wrong.cpp``` il sorgente del quale si vuole verificare la correttezza, ```checker.cpp```: un sorgente per controllare che l'output di ```wrong.cpp``` sia corretto, ```generator.cpp```: un sorgente che prende come parametro una stringa rappresentante un possibile seed da utilizzare per la generazione del testcase corrente, e stampa un'istanza sulla quale verranno confrontati i risultati di ```wrong.cpp``` e ```correct.cpp```, ```TL```: il tempo limite in secondi entro il quale dovrà terminare l'esecuzione di ```wrong.cpp```, ```TC```: il numero di istanze utilizzate per verificare la correttezza, ```safe```: un valore booleano il cui valore positivo indica che ```wrong.cpp``` verrà compilata con delle particolari flags ([sanitizers](https://docs.microsoft.com/en-us/cpp/sanitizers/?view=msvc-160)) che permetteno di rilevare errori a runtime. ### Output L'output prodotto dal tester sarà uno dei seguenti: - L'output prodotto da ```wrong.cpp``` risulta corretto per tutte le istanze provate, ed il suo tempo d'esecuzione non ha mai superato il time limit. - Compilazione di ```generator.cpp``` fallita. - Compilazione di ```checker.cpp``` fallita. - Compilazione di ```wrong.cpp``` fallita. - Compilazione di ```correct.cpp``` fallita. - Esecuzione di ```generator.cpp``` fallita. - Esecuzione di ```correct.cpp``` fallita. - Esecuzione di ```wrong.cpp``` fallita. - L'esecuzione di ```wrong.cpp``` ha superato il tempo limite per almeno un'istanza di input. - Viene trovato un'istanza per la quale ```wrong.cpp``` produce un output non considerato corretto rispetto a quello di ```correct.cpp```, ciò succede quando l'esecuzione di ```checker.cpp``` fallisce. Nel caso in cui il resoconto sia rappresentato da uno dei valori compresi tra 1 e 7 allora verranno forniti dettagli riguardo il motivo di tale errore (l'errore di compilazione nel caso si tratti di compilazione fallita, il contenuto di ```stderr``` nel caso in cui si tratti di un'esecuzione fallita). Nel caso in cui si ottenga come output il valore 8 verrà fornita l'istanza di input sulla quale l'esecuzione di ```wrong.cpp``` ha sforato il time limit ed l'output prodotto da ```correct.cpp``` su tale istanza. Infine se l'output del tester corrisponde a 9 allora verranno forniti: l'istanza di input sulla quale l'esecuzione di ```wrong.cpp``` ha prodotto un output non considerato corretto, l'output prodotto da ```wrong.cpp``` e l'output prodotto da ```correct.cpp```. ### Funzionamento Per funzionare velocemente il tester utilizza un numero di processi (che chiameremo workers) pari al numero di core della cpu sulla quale viene eseguito, tali processi si divideranno equamente il numero di istanze da testare e possono comunicare tra loro tramite una flag binaria condivisa. Il tester si occupa inizialmente della compilazione di ```generator.cpp```, ```checker.cpp```, ```correct.cpp``` e ```wrong.cpp``` ed una volta ottenuto il binario ne crea un copia per ogni singolo worker che lavorerà in una sua cartella indipendente. A questo punto ogni worker inizia a generare e controllare i risultati prodotti per un determinato numero di istanze (```TC```/ numero di workers), nel caso in cui venga trovata un'istanza per la quale l'output di ```wrong.cpp``` non viene considerato corretto allora l'esecuzione di tale worker termina e tramite la flag binaria condivisa vengono avvisati anche gli altri workers in modo che a loro volta possano terminare la propria esecuzione. Per effettuale l'esecuzione dei vari file binari necessari viene fatto un uso intensivo del modulo [subprocess](https://docs.python.org/3/library/subprocess.html), in particolare della funzione ```subprocess.run()``` ### Applicazione Web La versione web di questo strumento è rappresentata attraverso 2 possibili schermate: - New Submission: è la schermata che permette di effettuare uno stresstest impostando gli input dello strumento. Inviata la richiesta all'applicazione, quest'ultima eseguirà lo stresstest ed il risultato verrà memorizzato all'interno del database utilizzato. - Show Submissions: permette di osservare le informazioni delle ultime 10 sottoposizioni memorizzate all'interno del database dell'applicazione. ## Pipeline ### Build Lo stage di build si occupa di preparare tutte le risorse che saranno successivamente utilizzate dalla pipeline. Questa fase è composta dall'installazione di [pip](https://pip.pypa.io/en/stable/), la creazione e l'attivazione dell'ambiente virtuale, l'installazione dei requisiti contenuti in ```requirements.txt```, e dall'utilizzo di artefatti per memorizzare l'ambiente virtuale appena inizializzato per le fasi successive. ### Verify Lo stage di verify si occupa di effettuare un controllo statico dei codici del pacchetto sviluppato. Questo permette di rilevare potenziali errori oppure il mancato utilizzo di convezioni senza dover eseguire il codice. La fase di verify è attuata tramite due jobs, entrambi iniziano con l'attivazione dell'ambiente virtuale. I due jobs utilizzano rispettivamente [prospector](http://prospector.landscape.io/en/master/) e [bandit](https://pypi.org/project/bandit/0.13.1/) per analizzare staticamente il codice. ### Unit Test Lo stage di unit test consiste nel sottoporre il codice prodotto ad una serie di possibile istanze verificandone il giusto funzionamento. Nel nostro caso, per assicurarci del corretto funzionamento del tester, vengono effettuati una serie di test nei quali vengono utilizzati dei sorgenti di prova per poi confrontare gli output del tester con quelli che ci si aspettebbero utilizzando tali sorgenti. Per effettuare tale stage viene utilizzato il modulo [pytest](https://docs.pytest.org/en/6.2.x/) all'interno dell'ambiente virtuale. ### Package Lo stage di package è una fase che prevede la creazione del modulo relativo all'applicazione che si vuole distribuire. Questo consiste in due componenti: [Source Distribution](https://docs.python.org/3/distutils/sourcedist.html) e [Built Distribution](https://docs.python.org/3/distutils/builtdist.html) (ottenuti attraverso l'utilizzo dei package [setuptools](https://pypi.org/project/setuptools/), [build](https://pypi.org/project/build/) e [wheel](https://pypi.org/project/wheel/)). ### Release Questa fase della pipeline prevede il rilascio dell'applicazione, ovvero nel nostro caso specifico il rilascio del package [multiprocessing-stress-tester](https://pypi.org/project/multiprocessing-stress-tester/) nel ```Python Package Index```([PyPI](https://pypi.org/)) attraverso il modulo [twine](https://pypi.org/project/twine/). Vengono inoltre indicati username e password dell'account su [PyPI](https://pypi.org/) come variabili d'ambiente nel CI/CD Settings (rispettivamente come ```TWINE_USERNAME``` e ```TWINE_PASSWORD```). ### Deploy Lo stage finale della pipeline è quello di deploy, che permette di distribuire il modulo rilasciato in una piattaforma cloud (in questo caso [Heroku](https://www.heroku.com/)). Per poter hostare un'applicazione in Python, è necessario impostare il [Buildpack](https://devcenter.heroku.com/articles/buildpacks) relativo ad un'immagine di Python ([heroku-buildpack-python](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-python)) ed inoltre viene impostato anche [heroku-buildpack-apt](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-apt), il buildpack che permette di installare dipendenze di Ubuntu per il funzionamento dell'app. Per poter eseguire il deploy con successo, vengono aggiornate le dipendenze dell'ultima release di [multiprocessing-stress-tester](https://pypi.org/project/multiprocessing-stress-tester/) nella repository di riferimento dell'app su [Heroku](https://www.heroku.com/). ## TO-DO: - Build: utilizzare la cache invece degli artifatti. - Package: nella [Source Distribution](https://docs.python.org/3/distutils/sourcedist.html) non vengono aggiunti i file di template(```index.html``` e ```scripts.js```), però si trovano presenti nella [Built Distribution](https://docs.python.org/3/distutils/builtdist.html) in quanto eseguendo l'installazione tramite [pip](https://pip.pypa.io/en/stable/), i file risultano presenti nel package. - Deploy: nonostante si utilizzi l'[heroku-buildpack-apt](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-apt) e sia presente il file ```Aptfile``` con le dipendenze necessarie, durante l'esecuzione su [Heroku](https://www.heroku.com/) il container non trova il comando ```g++```: una possibile soluzione alternativa potrebbe essere quella di creare un'immagine con le dipendenze già installate o comprendere e motivazioni dietro il malfunzionamento del container e correggerlo.