From e03aa886b5e959419f9b8ee795319765cf7b8713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 23 Oct 2026 16:26:39 +0200 Subject: [PATCH 1/6] Fix #15134 (gui: tests through AT-SPI) --- gui/cppcheck_de.ts | 507 +++++-- gui/cppcheck_es.ts | 507 +++++-- gui/cppcheck_fi.ts | 507 +++++-- gui/cppcheck_fr.ts | 507 +++++-- gui/cppcheck_it.ts | 507 +++++-- gui/cppcheck_ja.ts | 507 +++++-- gui/cppcheck_ka.ts | 507 +++++-- gui/cppcheck_ko.ts | 507 +++++-- gui/cppcheck_nl.ts | 507 +++++-- gui/cppcheck_ru.ts | 507 +++++-- gui/cppcheck_sr.ts | 507 +++++-- gui/cppcheck_sv.ts | 507 +++++-- gui/cppcheck_zh_CN.ts | 507 +++++-- gui/cppcheck_zh_TW.ts | 507 +++++-- gui/manualtest/docs/handbook.html | 351 +++++ gui/manualtest/docs/report.png | Bin 0 -> 149022 bytes gui/manualtest/gen.py | 231 ++++ gui/manualtest/guitest.py | 708 ++++++++++ gui/manualtest/mainwindow.md | 21 - gui/manualtest/projectfiledialog.md | 52 - gui/manualtest/report.py | 408 ++++++ gui/manualtest/run.py | 230 ++++ gui/manualtest/runner.py | 232 ++++ gui/manualtest/testfile.py | 404 ++++++ gui/manualtest/tests/analysis.json | 457 +++++++ gui/manualtest/tests/application.json | 607 +++++++++ gui/manualtest/tests/preferences.json | 357 +++++ gui/manualtest/tests/project_dialog.json | 428 ++++++ gui/manualtest/tests/projects.json | 855 ++++++++++++ .../projects/basic-project/basic.cppcheck | 9 + .../projects/basic-project/include/util.h | 5 + .../tests/projects/basic-project/src/a.c | 18 + .../tests/projects/basic-project/src/b.c | 5 + gui/manualtest/tests/projects/custom-cfg/a.c | 4 + .../tests/projects/custom-cfg/aa.cfg | 402 ++++++ .../tests/projects/custom-cfg/p.cppcheck | 6 + .../tests/projects/embedded/board.h | 10 + .../tests/projects/embedded/embedded.cppcheck | 9 + gui/manualtest/tests/projects/embedded/gpio.c | 6 + gui/manualtest/tests/projects/embedded/main.c | 10 + .../projects/embedded/rules with spaces.txt | 7 + .../tests/projects/embedded/rules.txt | 7 + gui/manualtest/tests/projects/embedded/uart.c | 6 + gui/manualtest/tests/projects/recheck/a.c | 5 + gui/manualtest/tests/projects/recheck/a_v2.c | 5 + gui/manualtest/tests/projects/recheck/b.c | 5 + .../tests/projects/recheck/p.cppcheck | 7 + .../relative-paths/compile_commands.json | 4 + .../tests/projects/relative-paths/inc/util.h | 1 + .../tests/projects/relative-paths/skip/b.c | 4 + .../tests/projects/relative-paths/src/a.c | 6 + .../tests/projects/switch-project/one/a.c | 5 + .../projects/switch-project/one/p1.cppcheck | 6 + .../tests/projects/switch-project/two/b.c | 5 + .../projects/switch-project/two/p2.cppcheck | 6 + .../tests/projects/syntax-error/binary.c | Bin 0 -> 4096 bytes .../projects/syntax-error/binary.cppcheck | 7 + .../tests/projects/syntax-error/braces.c | 3 + .../projects/syntax-error/braces.cppcheck | 7 + .../tests/projects/syntax-error/empty.c | 0 .../projects/syntax-error/empty.cppcheck | 7 + .../tests/projects/syntax-error/latin1.c | 6 + .../projects/syntax-error/latin1.cppcheck | 7 + .../tests/projects/syntax-error/long_line.c | 1 + .../projects/syntax-error/long_line.cppcheck | 7 + .../tests/projects/syntax-error/ok.c | 5 + .../projects/syntax-error/unclosed_nesting.c | 4 + .../syntax-error/unclosed_nesting.cppcheck | 7 + gui/manualtest/tests/projects/xml-results/a.c | 5 + .../tests/projects/xml-results/results.xml | 9 + gui/manualtest/tests/result_list.json | 1198 +++++++++++++++++ gui/manualtest/tests/xml_results.json | 458 +++++++ gui/manualtest/web.py | 462 +++++++ gui/manualtest/web/index.html | 648 +++++++++ 74 files changed, 14650 insertions(+), 1193 deletions(-) create mode 100644 gui/manualtest/docs/handbook.html create mode 100644 gui/manualtest/docs/report.png create mode 100755 gui/manualtest/gen.py create mode 100755 gui/manualtest/guitest.py delete mode 100644 gui/manualtest/mainwindow.md delete mode 100644 gui/manualtest/projectfiledialog.md create mode 100644 gui/manualtest/report.py create mode 100755 gui/manualtest/run.py create mode 100644 gui/manualtest/runner.py create mode 100644 gui/manualtest/testfile.py create mode 100644 gui/manualtest/tests/analysis.json create mode 100644 gui/manualtest/tests/application.json create mode 100644 gui/manualtest/tests/preferences.json create mode 100644 gui/manualtest/tests/project_dialog.json create mode 100644 gui/manualtest/tests/projects.json create mode 100644 gui/manualtest/tests/projects/basic-project/basic.cppcheck create mode 100644 gui/manualtest/tests/projects/basic-project/include/util.h create mode 100644 gui/manualtest/tests/projects/basic-project/src/a.c create mode 100644 gui/manualtest/tests/projects/basic-project/src/b.c create mode 100644 gui/manualtest/tests/projects/custom-cfg/a.c create mode 100644 gui/manualtest/tests/projects/custom-cfg/aa.cfg create mode 100644 gui/manualtest/tests/projects/custom-cfg/p.cppcheck create mode 100644 gui/manualtest/tests/projects/embedded/board.h create mode 100644 gui/manualtest/tests/projects/embedded/embedded.cppcheck create mode 100644 gui/manualtest/tests/projects/embedded/gpio.c create mode 100644 gui/manualtest/tests/projects/embedded/main.c create mode 100644 gui/manualtest/tests/projects/embedded/rules with spaces.txt create mode 100644 gui/manualtest/tests/projects/embedded/rules.txt create mode 100644 gui/manualtest/tests/projects/embedded/uart.c create mode 100644 gui/manualtest/tests/projects/recheck/a.c create mode 100644 gui/manualtest/tests/projects/recheck/a_v2.c create mode 100644 gui/manualtest/tests/projects/recheck/b.c create mode 100644 gui/manualtest/tests/projects/recheck/p.cppcheck create mode 100644 gui/manualtest/tests/projects/relative-paths/compile_commands.json create mode 100644 gui/manualtest/tests/projects/relative-paths/inc/util.h create mode 100644 gui/manualtest/tests/projects/relative-paths/skip/b.c create mode 100644 gui/manualtest/tests/projects/relative-paths/src/a.c create mode 100644 gui/manualtest/tests/projects/switch-project/one/a.c create mode 100644 gui/manualtest/tests/projects/switch-project/one/p1.cppcheck create mode 100644 gui/manualtest/tests/projects/switch-project/two/b.c create mode 100644 gui/manualtest/tests/projects/switch-project/two/p2.cppcheck create mode 100644 gui/manualtest/tests/projects/syntax-error/binary.c create mode 100644 gui/manualtest/tests/projects/syntax-error/binary.cppcheck create mode 100644 gui/manualtest/tests/projects/syntax-error/braces.c create mode 100644 gui/manualtest/tests/projects/syntax-error/braces.cppcheck create mode 100644 gui/manualtest/tests/projects/syntax-error/empty.c create mode 100644 gui/manualtest/tests/projects/syntax-error/empty.cppcheck create mode 100644 gui/manualtest/tests/projects/syntax-error/latin1.c create mode 100644 gui/manualtest/tests/projects/syntax-error/latin1.cppcheck create mode 100644 gui/manualtest/tests/projects/syntax-error/long_line.c create mode 100644 gui/manualtest/tests/projects/syntax-error/long_line.cppcheck create mode 100644 gui/manualtest/tests/projects/syntax-error/ok.c create mode 100644 gui/manualtest/tests/projects/syntax-error/unclosed_nesting.c create mode 100644 gui/manualtest/tests/projects/syntax-error/unclosed_nesting.cppcheck create mode 100644 gui/manualtest/tests/projects/xml-results/a.c create mode 100644 gui/manualtest/tests/projects/xml-results/results.xml create mode 100644 gui/manualtest/tests/result_list.json create mode 100644 gui/manualtest/tests/xml_results.json create mode 100644 gui/manualtest/web.py create mode 100644 gui/manualtest/web/index.html diff --git a/gui/cppcheck_de.ts b/gui/cppcheck_de.ts index f89eb60e4a8..ee401f8c2a4 100644 --- a/gui/cppcheck_de.ts +++ b/gui/cppcheck_de.ts @@ -5,27 +5,32 @@ About + About Cppcheck Über Cppcheck + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Ein Werkzeug zur statischen C/C++-Code-Analyse. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-%1 Cppcheck-Team. + This program is licensed under the terms of the GNU General Public License version 3 Dieses Programm ist unter den Bedingungen @@ -33,11 +38,13 @@ der GNU General Public License Version 3 lizenziert + Visit Cppcheck homepage at %1 Besuchen Sie die Cppcheck-Homepage unter %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> <html><head/><body><p>Vielen Dank den Bibliotheken die wir nutzen:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> @@ -47,11 +54,13 @@ der GNU General Public License Version 3 lizenziert ApplicationDialog + Add an application Anwendung hinzufügen + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -78,21 +87,25 @@ Parameter: -l(line) (file) + &Name: &Name: + &Executable: &Ausführbare Datei: + &Parameters: &Parameter: + Browse Suchen @@ -188,16 +201,19 @@ Parameter: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck GUI Hilfe + Contents Inhalt + Index Index @@ -215,16 +231,19 @@ Parameter: -l(line) (file) LibraryAddFunctionDialog + Add function Funktion hinzufügen + Function name(s) Funktionsname(n) + Number of arguments Anzahl Argumente @@ -233,86 +252,103 @@ Parameter: -l(line) (file) LibraryDialog + Library Editor Bibliothekseditor + Open Öffnen + Save Speichern + Save as Speichern unter + Functions Funktionen + Sort Sortiere + Add Hinzufügen + Filter: Filter: + Comments Kommentare + noreturn Zurückkehrend + False Ja + True Nein + Unknown Unbekannt + return value must be used Rückgabewert muss genutzt werden + ignore function in leaks checking Ignoriere Funktion in Speicherleck-Prüfung + Arguments Argumente + Edit Bearbeiten @@ -356,11 +392,13 @@ Parameter: -l(line) (file) LibraryEditArgDialog + Edit argument Argument bearbeiten + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -376,11 +414,13 @@ Parameter: -l(line) (file) + Not bool Nicht boolsch + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -396,78 +436,99 @@ Parameter: -l(line) (file) + Not null Nicht Null + Not uninit Nicht uninitialisiert + String String + Format string Formatstring + Min size of buffer Minimale Puffergröße + + Type Typ + + None Keine + + argvalue Argumentwert + + mul Multiplikation + + strlen strlen + + Arg Argument 1 + + Arg2 Argument 2 + and und + Valid values Zulässige Werte @@ -480,506 +541,611 @@ Parameter: -l(line) (file) + Standard Standard + &File &Datei + &View &Ansicht + &Toolbars &Symbolleisten + A&nalyze A&nalysieren + C++ standard C++-Standard + &C standard &C-Standard + &Edit &Bearbeiten + &License... &Lizenz... + A&uthors... &Autoren... + &About... Ü&ber... + &Files... &Dateien... + + Analyze files Analysiere Dateien + Ctrl+F Strg+F + &Directory... &Verzeichnis... + + Analyze directory Analysiere Verzeichnis + Ctrl+D Strg+D + Ctrl+R Strg+R + &Stop &Stoppen + + Stop analysis Analyse abbrechen + Esc Esc + &Save results to file... &Ergebnisse in Datei speichern... + Ctrl+S Strg+S + &Quit &Beenden + &Clear results Ergebnisse &löschen + &Preferences &Einstellungen - + + + Show errors Zeige Fehler - + + + Show warnings Zeige Warnungen + + Show performance warnings Zeige Performance-Warnungen + Show &hidden Zeige &versteckte - - + + Information Information + Show information messages Zeige Informationsmeldungen + Show portability warnings Zeige Portabilitätswarnungen + Show Cppcheck results Zeige Cppcheck-Ergebnisse + Clang Clang + Show Clang results Zeige Clang-Ergebnisse + &Filter &Filter + Filter results Gefilterte Ergebnisse + Windows 32-bit ANSI Windows 32-bit, ANSI + Windows 32-bit Unicode Windows 32-bit, Unicode + Unix 32-bit Unix 32-bit + Unix 64-bit Unix 64-bit + Windows 64-bit Windows 64-bit + &Print... Drucken... + Print the Current Report Aktuellen Bericht ausdrucken + Print Pre&view... Druckvorschau + Open a Print Preview Dialog for the Current Results Druckvorschaudialog für aktuelle Ergebnisse öffnen + Open library editor Bibliothekseditor öffnen + &Check all Alle &auswählen + Checking for updates + Hide Verstecken + + Report + Filter Filter + &Reanalyze modified files Veränderte Dateien neu analysieren + Reanal&yze all files Alle Dateien erneut anal&ysieren + Ctrl+Q + Style war&nings Stilwar&nungen + E&rrors F&ehler + &Uncheck all Alle a&bwählen + Collapse &all Alle &reduzieren + &Expand all Alle &erweitern + &Standard &Standard + Standard items Standardeinträge + Toolbar Symbolleiste + &Categories &Kategorien + Error categories Fehler-Kategorien + &Open XML... Öffne &XML... + Open P&roject File... Pr&ojektdatei öffnen... + Ctrl+Shift+O + Sh&ow Scratchpad... &Zeige Schmierzettel... + &New Project File... &Neue Projektdatei... + Ctrl+Shift+N + &Log View &Loganzeige + Log View Loganzeige + C&lose Project File Projektdatei &schließen + &Edit Project File... Projektdatei &bearbeiten... + &Statistics &Statistik + &Warnings &Warnungen + Per&formance warnings Per&formance-Warnungen + &Information &Information + &Portability &Portabilität + P&latforms P&lattformen + C++&11 C++&11 + C&99 C&99 + &Posix Posix + C&11 C&11 + &C89 &C89 + &C++03 &C++03 + &Library Editor... &Bibliothekseditor + &Auto-detect language Sprache &automatisch erkennen + &Enforce C++ C++ &erzwingen + E&nforce C C e&rzwingen + C++14 C++14 + Reanalyze and check library Neu analysieren und Bibliothek prüfen + Check configuration (defines, includes) Prüfe Konfiguration (Definitionen, Includes) + C++17 C++17 + C++20 C++20 + Compliance report... + Normal @@ -997,68 +1163,84 @@ Parameter: -l(line) (file) + Autosar + EULA... + Thread Details + Show thread details + &Contents &Inhalte + Categories Kategorien + + Show style warnings Zeige Stilwarnungen + Open the help contents Öffnet die Hilfe-Inhalte + F1 F1 + &Help &Hilfe - - + + Quick Filter: Schnellfilter: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration Konfiguration wählen - + Found project file: %1 Do you want to load this project file instead? @@ -1067,102 +1249,102 @@ Do you want to load this project file instead? Möchten Sie stattdessen diese öffnen? - + File not found Datei nicht gefunden - + Bad XML Fehlerhaftes XML - + Missing attribute Fehlendes Attribut - + Bad attribute value Falscher Attributwert - + Duplicate platform type Plattformtyp doppelt - + Platform type redefined Plattformtyp neu definiert - + Duplicate define - + Failed to load the selected library '%1'. %2 Laden der ausgewählten Bibliothek '%1' schlug fehl. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Lizenz - + Authors Autoren - + Save the report file Speichert die Berichtdatei - - + + XML files (*.xml) XML-Dateien (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1171,32 +1353,32 @@ This is probably because the settings were changed between the Cppcheck versions Dies wurde vermutlich durch einen Wechsel der Cppcheck-Version hervorgerufen. Bitte prüfen (und korrigieren) Sie die Einstellungen, andernfalls könnte die Editor-Anwendung nicht korrekt starten. - + You must close the project file before selecting new files or directories! Sie müssen die Projektdatei schließen, bevor Sie neue Dateien oder Verzeichnisse auswählen! - + The library '%1' contains unknown elements: %2 Die Bibliothek '%1' enthält unbekannte Elemente: %2 - + Unsupported format Nicht unterstütztes Format - + Unknown element Unbekanntes Element - - - - + + + + Error Fehler @@ -1205,50 +1387,50 @@ Dies wurde vermutlich durch einen Wechsel der Cppcheck-Version hervorgerufen. Bi Laden von %1 fehlgeschlagen. Ihre Cppcheck-Installation ist defekt. Sie können --data-dir=<Verzeichnis> als Kommandozeilenparameter verwenden, um anzugeben, wo die Datei sich befindet. Bitte beachten Sie, dass --data-dir in Installationsroutinen genutzt werden soll, und die GUI bei dessen Nutzung nicht startet, sondern die Einstellungen konfiguriert. - + Open the report file Berichtdatei öffnen - + Text files (*.txt) Textdateien (*.txt) - + CSV files (*.csv) CSV-Dateien (*.csv) - + Project files (*.cppcheck);;All files(*.*) Projektdateien (*.cppcheck);;Alle Dateien(*.*) - + Select Project File Projektdatei auswählen - - - - + + + + Project: Projekt: - + No suitable files found to analyze! Keine passenden Dateien für Analyse gefunden! - + C/C++ Source C/C++-Quellcode - + Compile database Compilerdatenbank @@ -1261,22 +1443,22 @@ Dies wurde vermutlich durch einen Wechsel der Cppcheck-Version hervorgerufen. Bi Borland C++-Builder 6 - + Select files to analyze Dateien für Analyse auswählen - + Select directory to analyze Verzeichnis für Analyse auswählen - + Select the configuration that will be analyzed Zu analysierende Konfiguration auswählen - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1285,7 +1467,7 @@ Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1296,7 +1478,7 @@ Eine neue XML-Datei zu öffnen wird die aktuellen Ergebnisse löschen Möchten sie fortfahren? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1305,104 +1487,104 @@ Do you want to stop the analysis and exit Cppcheck? Wollen sie die Analyse abbrechen und Cppcheck beenden? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML-Dateien (*.xml);;Textdateien (*.txt);;CSV-Dateien (*.csv) - + Build dir '%1' does not exist, create it? Erstellungsverzeichnis '%1' existiert nicht. Erstellen? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1411,22 +1593,22 @@ Analysis is stopped. Import von '%1' fehlgeschlagen; Analyse wurde abgebrochen. - + Project files (*.cppcheck) Projektdateien (*.cppcheck) - + Select Project Filename Projektnamen auswählen - + No project file loaded Keine Projektdatei geladen - + The project file %1 @@ -1443,12 +1625,12 @@ Do you want to remove the file from the recently used projects -list? Möchten Sie die Datei von der Liste der zuletzt benutzten Projekte entfernen? - + Install - + New version available: %1. %2 @@ -1493,26 +1675,31 @@ Options: NewSuppressionDialog + New suppression Neue Fehlerunterdrückung + Error ID Fehler-ID + File name Dateiname + Line number Zeilennummer + Symbol name Symbolname @@ -1554,26 +1741,31 @@ Options: ProjectFile + Project File Projektdatei + Paths and Defines Pfade und Definitionen + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Importiere Projekt (Visual Studio / Compile-Datenbank / Borland C++-Builder 6) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Definitionen müssen mit einem Semikolon getrennt werden. Beispiel: DEF1;DEF2=5;DEF3=int + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. Hinweis: Legen Sie eigene .cfg-Dateien in den Ordner der Projektdatei. Dann sollten sie oben sichtbar werden. @@ -1583,48 +1775,59 @@ Options: + MISRA rule texts MISRA-Regeltexte + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Text aus Anhang A &quot;Summary of guidelines&quot; aus der MISRA-C-2012-PDF in eine Textdatei einfügen.</p></body></html> + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>Sie haben die Auswahl:</p><p> * Alle Debug- und Release-Konfigurationen analysieren</p><p> * Nur die erste passende Debug-Konfiguration analysieren</p><p><br/></p></body></html> + + Browse... Durchsuchen... + Analyze all Visual Studio configurations Alle Visual-Studio-Konfigurationen analysieren + Selected VS Configurations Ausgewählte VS-Konfiguration + Paths: Pfade: + + Add... Hinzufügen... @@ -1632,6 +1835,9 @@ Options: + + + Edit Bearbeiten @@ -1640,300 +1846,365 @@ Options: + + + + Remove Entfernen + Undefines: Un-Definitionen: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 Un-Definitionen müssen Semikolon-getrennt sein. Beispiel: UNDEF1;UNDEF2;UNDEF3 + Include Paths: Includepfade: + Up Auf + Down Ab + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Platform Plattform + + Analysis Analyse + This is a workfolder that Cppcheck will use for various purposes. + Clang (experimental) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) Prüfe Code in ungenutzten Templates (normalerweise AN, theoretisch können Warnungen in ungenutzten Templates gefahrlos ignoriert werden) + Max CTU depth Maximale CTU-Tiefe + Max recursion in template instantiation + Warning options Warnoptionen + Root path: Wurzelverzeichnis: + Filepaths in warnings will be relative to this path + Warning tags (separated by semicolon) Warnungs-Tags (Semikolon-getrennt) + Enable inline suppressions Inline-Fehlerunterdrückung aktivieren + 2025 + Misra C++ + 2008 + Cert C Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting + External tools Externe Werkzeuge + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck-Arbeitsverzeichnis (Vollständige Programmanalyse, inkrementelle Analyse, Statistiken, etc.) + Types and Functions + Libraries Bibliotheken + Parser + Cppcheck (built in) + Check that each class has a safe public interface + Limit analysis + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... + Suppressions Fehlerunterdrückungen + Add Hinzufügen + + Addons Add-Ons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 Y2038 + Thread safety Threadsicherheit + Coding standards Programmierstandards + Misra C Misra C + 2012 + + 2023 + Cert C++ Cert C++ + Bug hunting (Premium) @@ -1947,6 +2218,7 @@ Options: + Defines: Definitionen: @@ -2565,21 +2837,25 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde + Results Berichte + Critical errors Kritische Fehler + Analysis Log Analyseprotokoll + Warning Details Warnungs-Details @@ -2588,26 +2864,31 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde ScratchPad + Scratchpad Schmierzettel + Copy or write some C/C++ code here: Kopieren oder schreiben Sie C/C++-Code hierher: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": Optional einen Dateinamen (hauptsächlich für automatische Spracherkennung) eingeben und auf "Prüfe" klicken: + filename Dateiname + Check Prüfe @@ -2616,21 +2897,25 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde Settings + Preferences Einstellungen + General Allgemein + Add... Hinzufügen... + Number of threads: Anzahl der Threads: @@ -2640,56 +2925,67 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde + Force checking all #ifdef configurations Erzwinge Prüfung aller #ifdef-Konfigurationen + Show full path of files Vollständigen Dateipfad anzeigen + Show "No errors found" message when no errors found "Keine Fehler gefunden"-Meldung anzeigen, wenn keine Fehler gefunden werden + Display error Id in column "Id" Zeige Meldungs-Id in Spalte "Id" + Enable inline suppressions Inline-Fehlerunterdrückung aktivieren + Check for inconclusive errors also Auch nach unklaren Fehlern suchen + Show statistics on check completion Zeige Statistiken nach Prüfungsabschluss + Check for updates Auf Updates prüfen + Show internal warnings in log Interne Warnungen im Log anzeigen + Addons Add-Ons + Python binary (leave this empty to use python in the PATH) Python-Binärdatei (Python aus PATH wird genutzt, wenn leer) @@ -2697,117 +2993,143 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde + + + ... ... + MISRA addon MISRA-Addon + MISRA rule texts file MISRA-Regeltext-Datei + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Text aus Anhang A &quot;Summary of guidelines&quot; aus der MISRA-C-2012-PDF in eine Textdatei einfügen.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clang-Verzeichnis (PATH wird genutzt, wenn leer) + Visual Studio headers Visual-Studio-Header + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Pfade zu Visual-Studio-Headern, Semikolon-getrennt.</p><p>Sie können eine Visual-Studio-Kommandozeile öffnen, &quot;SET INCLUDE&quot; eingeben und dann die Pfade hier reinkopieren.</p></body></html> + Code Editor Code-Editor + Code Editor Style Code-Editor-Stil + System Style Systemstil + Default Light Style Heller Standardstil + Default Dark Style Dunkler Standardstil + Custom Benutzerdefiniert + Remove Entfernen + Max count: Maximalzahl: + Applications Anwendungen + + Edit... Bearbeiten... + Set as default Als Standard festlegen + Reports Berichte + Save all errors when creating report Alle Fehler beim Erstellen von Berichten speichern + Save full path to files in reports Vollständigen Dateipfad in Berichten speichern + Language Sprache @@ -2864,6 +3186,8 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde + + Statistics @@ -2871,113 +3195,135 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde + Project Projekt + Project: Projekt: + Paths: Pfade: + Include paths: Include-Pfade: + Defines: Definitionen: + Undefines: Un-Definitionen: + Previous Scan Vorherige Prüfung + Path Selected: Ausgewählte Pfade: + Number of Files Scanned: Anzahl der geprüften Dateien: + Scan Duration: Prüfungsdauer: + Errors: Fehler: + Warnings: Warnungen: + Stylistic warnings: Stilwarnungen: + Portability warnings: Portabilitätswarnungen: + Performance issues: Performance-Probleme: + Information messages: Informationsmeldungen: + Active checkers: Aktive Checker: + Checkers Checker + History Verlauf + File: Datei: + Copy to Clipboard In die Zwischenablage kopieren + Pdf Export PDF-Export @@ -3127,6 +3473,7 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde ThreadDetails + Thread Details diff --git a/gui/cppcheck_es.ts b/gui/cppcheck_es.ts index 0230aaa69e4..b2080055651 100644 --- a/gui/cppcheck_es.ts +++ b/gui/cppcheck_es.ts @@ -5,38 +5,45 @@ About + About Cppcheck Acerca de Cppcheck + Version %1 Versión %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Una utilidad para el análisis estático de código C/C++. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 el equipo de cppcheck. + This program is licensed under the terms of the GNU General Public License version 3 Este programa está licenciado bajo los términos de GNU General Public License versión 3 + Visit Cppcheck homepage at %1 Visita el sitio de Cppcheck en %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -46,11 +53,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application Añade una aplicación + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -66,21 +75,25 @@ Parameters: -l(line) (file) + &Name: &Nombre: + &Executable: &Ejecutable: + &Parameters: &Parámetros: + Browse Buscar @@ -125,16 +138,19 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index @@ -152,16 +168,19 @@ Parameters: -l(line) (file) LibraryAddFunctionDialog + Add function Añadir función + Function name(s) Nombre(s) de la función + Number of arguments Número de argumentos @@ -170,86 +189,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor Editor de bibliotecas + Open Abrir + Save Guardar + Save as + Functions Funciones + Sort + Add Añadir + Filter: + Comments + noreturn + False Falso + True Verdadero + Unknown Desconocido + return value must be used + ignore function in leaks checking + Arguments Argumentos + Edit Editar @@ -295,11 +331,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument Editar argumento + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -310,11 +348,13 @@ Parameters: -l(line) (file) + Not bool No bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -325,78 +365,99 @@ Parameters: -l(line) (file) + Not null No null + Not uninit No uninit + String String + Format string + Min size of buffer + + Type Tipo + + None Ninguno + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values Valores válidos @@ -409,684 +470,800 @@ Parameters: -l(line) (file) + &File &Archivo + &View &Ver + &Toolbars &Herramientas + &Help &Ayuda + C++ standard C++ estándar + &C standard C standard C estándar + &Edit &Editar + Standard Estándar + Categories Categorías + &License... &Licencia... + A&uthors... A&utores... + &About... &Acerca de... + &Files... &Ficheros... + + Analyze files Check files Comprobar archivos + Ctrl+F Ctrl+F + &Directory... &Carpeta... + + Analyze directory Check directory Comprobar carpeta + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop &Detener + + Stop analysis Stop checking Detener comprobación + Esc Esc + &Save results to file... &Guardar los resultados en el fichero... + Ctrl+S Ctrl+S + &Quit &Salir + &Clear results &Limpiar resultados + &Preferences &Preferencias + + Show style warnings Mostrar advertencias de estilo - + + + Show errors Mostrar errores - - + + Information Información + Show information messages Mostrar mensajes de información + Show portability warnings Mostrar advertencias de portabilidad + Show Cppcheck results + Clang + Show Clang results + &Filter &Filtro + Filter results Resultados del filtro + Windows 32-bit ANSI Windows 32-bit ANSI + Windows 32-bit Unicode Windows 32-bit Unicode + Unix 32-bit Unix 32-bit + Unix 64-bit Unix 64-bit + Windows 64-bit Windows 64-bit + &Print... Im&primir... + Print the Current Report Imprimir el informe actual + Print Pre&view... Pre&visualización de impresión... + Open a Print Preview Dialog for the Current Results Abre el diálogo de previsualización de impresión para el informe actual + Open library editor Abrir el editor de bibliotecas + &Check all &Seleccionar todo + Checking for updates + Hide Ocultar + + Report + A&nalyze + Filter Filtro + &Reanalyze modified files &Recheck modified files + Reanal&yze all files + Ctrl+Q + Style war&nings + E&rrors + &Uncheck all &Deseleccionar todo + Collapse &all Contraer &todo + &Expand all &Expandir todo + &Standard &Estándar + Standard items Elementos estándar + &Contents &Contenidos + Open the help contents Abrir la ayuda de contenidos + F1 F1 + Toolbar Barra de herramientas + &Categories &Categorías + Error categories Categorías de error + &Open XML... &Abrir XML... + Open P&roject File... Abrir P&royecto... + Ctrl+Shift+O + Sh&ow Scratchpad... + &New Project File... &Nuevo Proyecto... + Ctrl+Shift+N + &Log View &Visor del log + Log View Visor del log + C&lose Project File C&errar Proyecto + &Edit Project File... &Editar Proyecto... + &Statistics &Estadísticas + &Warnings + Per&formance warnings + &Information + &Portability + P&latforms + C++&11 + C&99 + &Posix + C&11 + &C89 + &C++03 + &Library Editor... + &Auto-detect language + &Enforce C++ + E&nforce C + C++14 C++14 + Reanalyze and check library + Check configuration (defines, includes) + C++17 C++17 + C++20 C++20 + Compliance report... + Normal + Autosar + EULA... + Thread Details + Show thread details - + + + Show warnings Mostrar advertencias + + Show performance warnings Mostrar advertencias de rendimiento + Show &hidden Mostrar &ocultos - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + You must close the project file before selecting new files or directories! ¡Tienes que cerrar el proyecto antes de seleccionar nuevos ficheros o carpetas! - + Select configuration - + File not found Archivo no encontrado - + Bad XML XML malformado - + Missing attribute Falta el atributo - + Bad attribute value - + Unsupported format Formato no soportado - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - - + + XML files (*.xml) Archivos XML (*.xml) - + Open the report file Abrir informe - + License Licencia - + Authors Autores - + Save the report file Guardar informe - - + + Quick Filter: Filtro rápido: - + Found project file: %1 Do you want to load this project file instead? @@ -1095,102 +1272,107 @@ Do you want to load this project file instead? ¿Quiere cargar este fichero de proyecto en su lugar? - + The library '%1' contains unknown elements: %2 La biblioteca '%1' contiene elementos deconocidos: %2 - + Duplicate platform type - + Platform type redefined - + Unknown element - - - - + + + + Error Error - + Text files (*.txt) Ficheros de texto (*.txt) - + CSV files (*.csv) Ficheros CVS (*.cvs) - + Project files (*.cppcheck);;All files(*.*) Ficheros de proyecto (*.cppcheck;;Todos los ficheros (*.*) - + Select Project File Selecciona el archivo de proyecto - - - - + + + + Project: Proyecto: - + + None of the selected files is part of the project, nothing to analyze. + + + + No suitable files found to analyze! - + C/C++ Source - + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1198,76 +1380,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename Selecciona el nombre del proyecto - + No project file loaded No hay ningún proyecto cargado - + The project file %1 @@ -1284,67 +1466,67 @@ Do you want to remove the file from the recently used projects -list? ¿Quiere eliminar el fichero de la lista de proyectos recientes? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1388,26 +1570,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1449,111 +1636,135 @@ Options: ProjectFile + Project File Archivo de proyecto + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. Nota: Ponga sus propios archivos .cfg en la misma carpeta que el proyecto. Debería verlos arriba. + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... + Misra C + 2012 + + 2023 + MISRA rule texts + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: Rutas: + + Add... Añadir... @@ -1561,6 +1772,9 @@ Options: + + + Edit Editar @@ -1569,269 +1783,327 @@ Options: + + + + Remove Eliminar + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Types and Functions + + Analysis + This is a workfolder that Cppcheck will use for various purposes. + Parser + Cppcheck (built in) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + Check that each class has a safe public interface + Limit analysis + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) + Max CTU depth + Enable inline suppressions Habilitar supresiones inline + 2025 + Misra C++ + 2008 + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting + External tools + Up Subir + Down Bajar + Platform + Clang (experimental) + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + Max recursion in template instantiation + Warning options + Root path: + Filepaths in warnings will be relative to this path + Warning tags (separated by semicolon) + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + Libraries + Suppressions Supresiones + Add Añadir + + Addons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 + Thread safety + Coding standards + Cert C++ + Bug hunting (Premium) + Defines: Definiciones: @@ -2354,21 +2626,25 @@ Por favor comprueba que la ruta a la aplicación y los parámetros son correctos ResultsView + Results Resultados + Critical errors + Analysis Log + Warning Details @@ -2475,26 +2751,31 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. ScratchPad + Scratchpad Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename nombre de archivo + Check Comprobar @@ -2503,11 +2784,13 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. Settings + Preferences Preferencias + General General @@ -2517,46 +2800,55 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. + Force checking all #ifdef configurations Forzar comprobación de todas las configuraciones #ifdef + Display error Id in column "Id" Mostrar el Id del error en la columna "Id" + Enable inline suppressions Habilitar supresiones inline + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2564,137 +2856,167 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Add... Añadir... + Max count: + + Edit... Editar... + Set as default Definir por defecto + Language Idioma + Number of threads: Número de hilos: + Show full path of files Mostrar la ruta completa de los ficheros + Show "No errors found" message when no errors found Mostrar el mensaje "No se han encontrado errores" + Remove Eliminar + Applications Aplicaciones + Reports Informes + Save all errors when creating report Guardar todos los errores cuando se cree el informe + Save full path to files in reports Guardar la ruta completa en los ficheros de informes @@ -2751,6 +3073,8 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. + + Statistics @@ -2758,113 +3082,135 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. + Project Proyecto + Project: Proyecto: + Paths: Rutas: + Include paths: Incluye las rutas: + Defines: Definiciones: + Undefines: + Previous Scan Análisis anterior + Path Selected: Ruta seleccionada: + Number of Files Scanned: Número de archivos analizados: + Scan Duration: Duración del análisis: + Errors: Errores: + Warnings: Advertencias: + Stylistic warnings: Advertencias de estilo: + Portability warnings: Advertencias de portabilidad: + Performance issues: Problemas de rendimiento: + Information messages: Mensajes de información: + Active checkers: + Checkers + History + File: + Copy to Clipboard Copiar al portapapeles + Pdf Export @@ -3014,6 +3360,7 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. ThreadDetails + Thread Details diff --git a/gui/cppcheck_fi.ts b/gui/cppcheck_fi.ts index 8bdd6fd1671..0600febbd04 100644 --- a/gui/cppcheck_fi.ts +++ b/gui/cppcheck_fi.ts @@ -5,27 +5,32 @@ About + About Cppcheck Tietoa ohjelmasta Cppcheck + Version %1 Versio %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Työkalu C/C++ koodin staattiseen analysointiin. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright (C) 2007-2021 Daniel Marjamäki ja cppcheck tiimi. + This program is licensed under the terms of the GNU General Public License version 3 Tämä ohjelma on lisensoitu GNU General @@ -33,11 +38,13 @@ Public lisenssin version 3 alaisuuteen + Visit Cppcheck homepage at %1 Cppcheckin kotisivu löytyy osoitteesta %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ Public lisenssin version 3 alaisuuteen ApplicationDialog + Add an application Lisää uusi ohjelma + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -67,21 +76,25 @@ Parameters: -l(line) (file) + &Name: + &Executable: + &Parameters: + Browse Selaa @@ -128,16 +141,19 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index @@ -155,16 +171,19 @@ Parameters: -l(line) (file) LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -173,86 +192,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor + Open + Save + Save as + Functions + Sort + Add + Filter: + Comments + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit @@ -298,11 +334,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -313,11 +351,13 @@ Parameters: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -328,78 +368,99 @@ Parameters: -l(line) (file) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -412,785 +473,906 @@ Parameters: -l(line) (file) + A&nalyze + Standard Vakio + &File &Tiedosto + &View &Näytä + &Toolbars + C++ standard + &C standard C standard + &Edit &Muokkaa + &License... &Lisenssi... + A&uthors... &Tekijät... + &About... &Tietoa ohjelmasta Cppcheck... + &Files... &Tiedostot... + + Analyze files Check files + Ctrl+F Ctrl+F + &Directory... &Hakemisto... + + Analyze directory Check directory + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop &Pysäytä + + Stop analysis Stop checking + Esc Esc + &Save results to file... &Tallenna tulokset tiedostoon... + Ctrl+S Ctrl+S + &Quit &Lopeta + &Clear results &Tyhjennä tulokset + &Preferences &Asetukset - + + + Show errors - + + + Show warnings + + Show performance warnings + Show &hidden - - + + Information + Show information messages + Show portability warnings + Show Cppcheck results + Clang + Show Clang results + &Filter + Filter results + Windows 32-bit ANSI + Windows 32-bit Unicode + Unix 32-bit + Unix 64-bit + Windows 64-bit + &Print... + Print the Current Report + Print Pre&view... + Open a Print Preview Dialog for the Current Results + Open library editor + &Check all &Valitse kaikki + Checking for updates + Hide + + Report + Filter + &Reanalyze modified files &Recheck modified files + Reanal&yze all files + Ctrl+Q + Style war&nings + E&rrors + &Uncheck all &Poista kaikista valinta + Collapse &all &Pienennä kaikki + &Expand all &Laajenna kaikki + &Standard + Standard items + Toolbar + &Categories + Error categories + &Open XML... + Open P&roject File... + Ctrl+Shift+O + Sh&ow Scratchpad... + &New Project File... + Ctrl+Shift+N + &Log View + Log View + C&lose Project File + &Edit Project File... + &Statistics + &Warnings + Per&formance warnings + &Information + &Portability + P&latforms + C++&11 + C&99 + &Posix + C&11 + &C89 + &C++03 + &Library Editor... + &Auto-detect language + &Enforce C++ + E&nforce C + C++14 + Reanalyze and check library + Check configuration (defines, includes) + C++17 + C++20 + Compliance report... + Normal + Autosar + EULA... + Thread Details + Show thread details + &Contents + Categories + + Show style warnings + Open the help contents + F1 + &Help &Ohje - - + + Quick Filter: - + Select configuration - + Found project file: %1 Do you want to load this project file instead? - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Lisenssi - + Authors Tekijät - + Save the report file Tallenna raportti - - + + XML files (*.xml) XML-tiedostot (*xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + You must close the project file before selecting new files or directories! - + The library '%1' contains unknown elements: %2 - + Unsupported format - + Duplicate platform type - + Platform type redefined - + Unknown element - - - - + + + + Error - + Open the report file - + Text files (*.txt) Tekstitiedostot (*.txt) - + CSV files (*.csv) - + Project files (*.cppcheck);;All files(*.*) - + Select Project File - - - - + + + + Project: - + + None of the selected files is part of the project, nothing to analyze. + + + + No suitable files found to analyze! - + C/C++ Source - + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1198,76 +1380,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename - + No project file loaded - + The project file %1 @@ -1278,67 +1460,67 @@ Do you want to remove the file from the recently used projects -list? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1382,26 +1564,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1423,75 +1610,91 @@ Options: ProjectFile + Project File + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. + MISRA rule texts + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: + + Add... @@ -1499,6 +1702,9 @@ Options: + + + Edit @@ -1507,305 +1713,371 @@ Options: + + + + Remove + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: + Up + Down + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Platform + + Analysis + This is a workfolder that Cppcheck will use for various purposes. + Clang (experimental) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) + Max CTU depth + Max recursion in template instantiation + Warning options + Root path: + Filepaths in warnings will be relative to this path + Warning tags (separated by semicolon) + Enable inline suppressions + 2025 + Misra C++ + 2008 + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting + External tools + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + Types and Functions + Libraries + Parser + Cppcheck (built in) + Check that each class has a safe public interface + Limit analysis + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... + Suppressions + Add + + Addons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 + Thread safety + Coding standards + Misra C + 2012 + + 2023 + Cert C++ + Bug hunting (Premium) + Defines: @@ -2412,21 +2684,25 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik + Results Tulokset + Critical errors + Analysis Log + Warning Details @@ -2435,26 +2711,31 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik ScratchPad + Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename + Check @@ -2463,77 +2744,92 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik Settings + Preferences Asetukset + General Yleiset + Add... + Number of threads: Säikeiden lukumäärä: + Force checking all #ifdef configurations Check all #ifdef configurations Tarkista kaikki #ifdef kombinaatiot + Show full path of files Näytä tiedostojen täysi polku + Show "No errors found" message when no errors found Näytä "virheitä ei löytynyt"-viesti jos virheitä ei löydy + Display error Id in column "Id" + Enable inline suppressions + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2541,117 +2837,143 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove + Max count: + Applications Ohjelmat + + Edit... + Set as default + Reports Raportit + Save all errors when creating report Tallenna kaikki virheet raporttia luodessa + Save full path to files in reports Tallenna tiedostojen koko polku raportteihin + Language @@ -2704,6 +3026,8 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik + + Statistics @@ -2711,113 +3035,135 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik + Project + Project: + Paths: + Include paths: + Defines: + Undefines: + Previous Scan + Path Selected: + Number of Files Scanned: + Scan Duration: + Errors: + Warnings: + Stylistic warnings: + Portability warnings: + Performance issues: + Information messages: + Active checkers: + Checkers + History + File: + Copy to Clipboard + Pdf Export @@ -2967,6 +3313,7 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik ThreadDetails + Thread Details diff --git a/gui/cppcheck_fr.ts b/gui/cppcheck_fr.ts index ca25352e60c..7ac6f3ae89a 100644 --- a/gui/cppcheck_fr.ts +++ b/gui/cppcheck_fr.ts @@ -5,21 +5,25 @@ About + About Cppcheck A propos + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Un outil d'analyse statique de code C/C++. + This program is licensed under the terms of the GNU General Public License version 3 Ce programme est sous licence GNU @@ -27,17 +31,20 @@ General Public License version 3 + Visit Cppcheck homepage at %1 Visitez le site Cppcheck : %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. @@ -47,11 +54,13 @@ General Public License version 3 ApplicationDialog + Add an application Ajouter une application + Browse Parcourir @@ -67,16 +76,19 @@ General Public License version 3 + &Executable: &Exécutable : + &Parameters: &Paramètres : + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -102,6 +114,7 @@ Paramètres : -l(ligne) (fichier) + &Name: &Nom : @@ -128,16 +141,19 @@ Paramètres : -l(ligne) (fichier) HelpDialog + Cppcheck GUI help + Contents + Index @@ -151,16 +167,19 @@ Paramètres : -l(ligne) (fichier) LibraryAddFunctionDialog + Add function Ajouter une fonction + Function name(s) Nom(s) de la fonction + Number of arguments Nombre d'arguments @@ -169,66 +188,79 @@ Paramètres : -l(ligne) (fichier) LibraryDialog + Library Editor + Open + Save Sauvegarder + Functions + Add Ajouter + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit Editer @@ -245,21 +277,25 @@ Paramètres : -l(ligne) (fichier) + Sort + Filter: + Comments + Save as @@ -288,11 +324,13 @@ Paramètres : -l(ligne) (fichier) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -303,11 +341,13 @@ Paramètres : -l(ligne) (fichier) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -318,78 +358,99 @@ Paramètres : -l(ligne) (fichier) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -398,472 +459,535 @@ Paramètres : -l(ligne) (fichier) MainWindow + Checking for updates + Hide Cacher + &File &Fichier + &View &Affichage + + Report + &Help &Aide + &Edit &Édition + Standard Standard + &License... &Licence... + A&uthors... A&uteurs... + &About... À &Propos... + &Files... &Fichiers... + Ctrl+F + &Directory... &Répertoires... + Ctrl+D + Ctrl+R + &Stop &Arrêter + Esc + &Save results to file... &Sauvegarder les résultats dans un fichier... + Ctrl+S + &Quit &Quitter + &Clear results &Effacer les résultats + &Preferences &Préférences + &Check all &Tout cocher + &Uncheck all &Tout décocher + Collapse &all &Tout réduire + &Expand all &Tout dérouler + &Contents &Contenus + Open the help contents Ouvir l'aide + F1 + Compliance report... + Normal + Autosar + EULA... + Thread Details + Show thread details - + License Licence - + Authors Auteurs - + Save the report file Sauvegarder le rapport - - + + XML files (*.xml) Fichiers XML (*.xml) - + About - + Text files (*.txt) Fichiers Texte (*.txt) - + CSV files (*.csv) Fichiers CSV (*.csv) + &Toolbars &Boite à outils + Categories Catégories + + Show style warnings Afficher les avertissements de style - + + + Show errors Afficher les erreurs + &Standard + Standard items + Toolbar + &Categories + Error categories + &Open XML... &Ouvrir un fichier XML... + Open P&roject File... Ouvrir un P&rojet... + &New Project File... &Nouveau Projet... + &Log View &Journal + Log View Journal + C&lose Project File F&ermer le projet + &Edit Project File... &Editer le projet + &Statistics Statistiques - + + + Show warnings Afficher les avertissements + + Show performance warnings Afficher les avertissements de performance + Show &hidden - - + + Information Information + Show information messages Afficher les messages d'information + Show portability warnings Afficher les problèmes de portabilité - + You must close the project file before selecting new files or directories! Vous devez d'abord fermer le projet avant de choisir des fichiers/répertoires - + Open the report file Ouvrir le rapport - + Project files (*.cppcheck);;All files(*.*) - + Select Project File - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Select Project Filename - + No project file loaded - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -871,37 +995,39 @@ This is probably because the settings were changed between the Cppcheck versions + &Filter &Filtre + Filter results - - + + Quick Filter: Filtre rapide : - + Found project file: %1 Do you want to load this project file instead? - - - - + + + + Project: Projet : - + The project file %1 @@ -913,31 +1039,37 @@ Do you want to remove the file from the recently used projects -list? + Filter Filtre + Windows 32-bit ANSI + Windows 32-bit Unicode + Unix 32-bit + Unix 64-bit + Windows 64-bit @@ -948,98 +1080,104 @@ Do you want to remove the file from the recently used projects -list? + C++ standard - - - - + + + + Error Erreur - + File not found Fichier introuvable - + Bad XML Mauvais fichier XML - + Missing attribute Attribut manquant - + Bad attribute value Mauvaise valeur d'attribut - + Failed to load the selected library '%1'. %2 Echec lors du chargement de la bibliothèque '%1'. %2 - + Unsupported format Format non supporté - + The library '%1' contains unknown elements: %2 La bibliothèque '%1' contient des éléments inconnus: %2 - + Duplicate platform type - + Platform type redefined + &Print... &Imprimer... + Print the Current Report Imprimer le rapport + Print Pre&view... Apercu d'impression... + Open a Print Preview Dialog for the Current Results + Open library editor - + Unknown element - + Select configuration @@ -1062,104 +1200,116 @@ Options: - + Build dir '%1' does not exist, create it? + + Analyze files + + Analyze directory + &Reanalyze modified files + + Stop analysis - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - + No suitable files found to analyze! - + + None of the selected files is part of the project, nothing to analyze. + + + + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Duplicate define - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1167,181 +1317,213 @@ Do you want to stop the analysis and exit Cppcheck? + A&nalyze + &C standard + Reanal&yze all files + Ctrl+Q + Style war&nings + E&rrors + Ctrl+Shift+O + Sh&ow Scratchpad... + Ctrl+Shift+N + &Warnings + Per&formance warnings + &Information + &Portability + Show Cppcheck results + Clang + Show Clang results + P&latforms + C++&11 + C&99 + &Posix + C&11 + &C89 + &C++03 + &Library Editor... + &Auto-detect language + &Enforce C++ + E&nforce C + C++14 - + Project files (*.cppcheck) + Reanalyze and check library + Check configuration (defines, includes) + C++17 + C++20 - + C/C++ Source - + Compile database - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1353,26 +1535,31 @@ Do you want to proceed? NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1394,22 +1581,27 @@ Do you want to proceed? ProjectFile + Project File Fichier Projet + Paths: Chemins : + Defines: + + Add... Ajouter... @@ -1417,6 +1609,9 @@ Do you want to proceed? + + + Edit Editer @@ -1425,356 +1620,433 @@ Do you want to proceed? + + + + Remove Supprimer + Up Monter + Down Descendre + Suppressions Suppressions + Add Ajouter + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. + ... + Include Paths: + Paths and Defines + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + Analyze all Visual Studio configurations + Root path: + Warning tags (separated by semicolon) + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + Selected VS Configurations + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Types and Functions + Libraries + Parser + Cppcheck (built in) + Check that each class has a safe public interface + Limit analysis + + Addons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 + Thread safety + Coding standards + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Bug hunting + + Browse... + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int + Platform + This is a workfolder that Cppcheck will use for various purposes. + Clang (experimental) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + Max recursion in template instantiation + Warning options + Filepaths in warnings will be relative to this path + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... + Enable inline suppressions + Misra C + 2012 + + 2023 + 2025 + MISRA rule texts + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Misra C++ + 2008 + Cert C++ + Safety profiles (defined in C++ core guidelines) + Bug hunting (Premium) + External tools + Import Project (Visual studio / compile database/ Borland C++ Builder 6) + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + + Analysis + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) + Max CTU depth @@ -2291,6 +2563,7 @@ Please select the default editor application in preferences/Applications.ResultsView + Results Résultats @@ -2375,16 +2648,19 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. + Critical errors + Analysis Log + Warning Details @@ -2408,26 +2684,31 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage.ScratchPad + Scratchpad + filename + Check + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": @@ -2436,122 +2717,147 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage.Settings + Preferences Préférences + General Général + Number of threads: Nombre de fils : + Max count: + Show full path of files Montrer le chemin complet des fichiers + Show "No errors found" message when no errors found Afficher un message "Pas d'erreur trouvée" lorsque aucune erreur est trouvée + Check for updates + Applications Applications + Reports Rapports + Save all errors when creating report Sauvegarder toutes les erreurs lorsqu'un rapport est créé + Save full path to files in reports Sauvegarder le chemin complet des fichiers dans les rapports + Add... Ajouter... + Force checking all #ifdef configurations + Enable inline suppressions + Language Langue + Remove Supprimer + + Edit... Editer... + Set as default + Display error Id in column "Id" Afficher l'identifiant d'erreur Id dans la colonne "Id" + Check for inconclusive errors also + Show internal warnings in log Montrer les avertissements internes dans le journal + Show statistics on check completion + Addons + Python binary (leave this empty to use python in the PATH) @@ -2559,71 +2865,87 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. + + + ... + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Code Editor + Code Editor Style + Default Light Style + Default Dark Style + Custom + System Style @@ -2676,6 +2998,8 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. + + Statistics @@ -2683,93 +3007,111 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. + Project Projet + Project: Projet : + Paths: Chemins : + Include paths: Inclure les chemins : + Defines: + Previous Scan Analyse précédente + Path Selected: Chemin sélectionné : + Number of Files Scanned: Nombre de fichiers analysés : + Scan Duration: Durée de l'analyse : + Errors: Erreurs : + Warnings: Avertissements + Stylistic warnings: Avertissements de style + Portability warnings: Avertissements de portabilité + Performance issues: Problème de performance + Information messages: Messages d'information : + Active checkers: + Checkers + Copy to Clipboard Copier vers le presse-papier @@ -2896,6 +3238,7 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. + Pdf Export @@ -2906,11 +3249,13 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. + History + File: @@ -2926,6 +3271,7 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. + Undefines: @@ -2939,6 +3285,7 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage.ThreadDetails + Thread Details diff --git a/gui/cppcheck_it.ts b/gui/cppcheck_it.ts index 243b0be7d51..4d0823a7053 100644 --- a/gui/cppcheck_it.ts +++ b/gui/cppcheck_it.ts @@ -5,27 +5,32 @@ About + About Cppcheck Informazioni su Cppcheck + Version %1 Versione %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Uno strumento per l'analisi statica di codice C/C++ + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 il team Cppcheck. + This program is licensed under the terms of the GNU General Public License version 3 Questo programma è rilasciato sotto i termini @@ -33,11 +38,13 @@ della GNU General Public License versione 3 + Visit Cppcheck homepage at %1 Visita la pagina iniziale di Cppcheck all'indirizzo: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ della GNU General Public License versione 3 ApplicationDialog + Add an application Aggiungi un'applicazione + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -78,21 +87,25 @@ Parametri: -l(line) (file) + &Name: &Nome: + &Executable: &Eseguibile: + &Parameters: &Parametri: + Browse Esplora @@ -137,16 +150,19 @@ Parametri: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index @@ -164,16 +180,19 @@ Parametri: -l(line) (file) LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -182,86 +201,103 @@ Parametri: -l(line) (file) LibraryDialog + Library Editor + Open + Save + Save as + Functions + Sort + Add + Filter: + Comments + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit Modifica @@ -307,11 +343,13 @@ Parametri: -l(line) (file) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -322,11 +360,13 @@ Parametri: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -337,78 +377,99 @@ Parametri: -l(line) (file) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -421,578 +482,694 @@ Parametri: -l(line) (file) + A&nalyze + Standard Standard + &File &File + &View &Visualizza + &Toolbars &Barre degli strumenti + C++ standard + &C standard C standard + &Edit &Modifica + &License... &Licenza... + A&uthors... A&utori... + &About... I&nformazioni su... + &Files... &File... + + Analyze files Check files Scansiona i file + Ctrl+F Ctrl+F + &Directory... &Cartella... + + Analyze directory Check directory Scansiona la cartella + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop &Ferma + + Stop analysis Stop checking Ferma la scansione + Esc Esc + &Save results to file... &Salva i risultati nel file... + Ctrl+S Ctrl+S + &Quit &Esci + &Clear results &Cancella i risultati + &Preferences &Preferenze - + + + Show errors Mostra gli errori - + + + Show warnings Mostra gli avvisi + + Show performance warnings Mostra gli avvisi sulle prestazioni + Show &hidden Mostra &i nascosti - - + + Information Informazione + Show information messages Mostra messaggi di informazione + Show portability warnings Mostra gli avvisi sulla portabilità + Show Cppcheck results + Clang + Show Clang results + &Filter &Filtro + Filter results Filtra i risultati + Windows 32-bit ANSI Windows 32-bit, ANSI + Windows 32-bit Unicode Windows 32-bit, Unicode + Unix 32-bit Unix 32-bit + Unix 64-bit Unix 64-bit + Windows 64-bit Windows 64-bit + &Print... + Print the Current Report + Print Pre&view... + Open a Print Preview Dialog for the Current Results + Open library editor + &Check all &Seleziona tutto + Checking for updates + Hide Nascondi + + Report + Filter Filtro + &Reanalyze modified files &Recheck modified files + Reanal&yze all files + Ctrl+Q + Style war&nings + E&rrors + &Uncheck all &Deseleziona tutto + Collapse &all Riduci &tutto + &Expand all &Espandi tutto + &Standard &Standard + Standard items Oggetti standard + Toolbar Barra degli strumenti + &Categories &Categorie + Error categories Categorie di errore + &Open XML... &Apri XML... + Open P&roject File... Apri file di p&rogetto... + Ctrl+Shift+O + Sh&ow Scratchpad... + &New Project File... &Nuovo file di progetto... + Ctrl+Shift+N + &Log View &Visualizza il rapporto + Log View Visualizza il rapporto + C&lose Project File C&hiudi il file di progetto + &Edit Project File... &Modifica il file di progetto... + &Statistics &Statistiche + &Warnings + Per&formance warnings + &Information + &Portability + P&latforms + C++&11 + C&99 + &Posix + C&11 + &C89 + &C++03 + &Library Editor... + &Auto-detect language + &Enforce C++ + E&nforce C + C++14 C++14 + Reanalyze and check library + Check configuration (defines, includes) + C++17 C++17 + C++20 C++20 + Compliance report... + Normal + Autosar + EULA... + Thread Details + Show thread details + &Contents &Contenuti + Categories Categorie + + Show style warnings Mostra gli avvisi sullo stile + Open the help contents Apri i contenuti di aiuto + F1 F1 + &Help &Aiuto - - + + Quick Filter: Rapido filtro: - + Select configuration - + Found project file: %1 Do you want to load this project file instead? @@ -1001,96 +1178,96 @@ Do you want to load this project file instead? Vuoi piuttosto caricare questo file di progetto? - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Unsupported format - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Licenza - + Authors Autori - + Save the report file Salva il file di rapporto - - + + XML files (*.xml) File XML (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1099,111 +1276,116 @@ This is probably because the settings were changed between the Cppcheck versions Probabilmente ciò è avvenuto perché le impostazioni sono state modificate tra le versioni di Cppcheck. Per favore controlla (e sistema) le impostazioni delle applicazioni editor, altrimenti il programma editor può non partire correttamente. - + You must close the project file before selecting new files or directories! Devi chiudere il file di progetto prima di selezionare nuovi file o cartelle! - + The library '%1' contains unknown elements: %2 - + Duplicate platform type - + Platform type redefined - + Unknown element - - - - + + + + Error - + Open the report file Apri il file di rapporto - + Text files (*.txt) File di testo (*.txt) - + CSV files (*.csv) Files CSV (*.csv) - + Project files (*.cppcheck);;All files(*.*) Files di progetto (*.cppcheck);;Tutti i files(*.*) - + Select Project File Seleziona il file di progetto - - - - + + + + Project: Progetto: - + + None of the selected files is part of the project, nothing to analyze. + + + + No suitable files found to analyze! - + C/C++ Source - + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1211,76 +1393,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename Seleziona il nome del file di progetto - + No project file loaded Nessun file di progetto caricato - + The project file %1 @@ -1297,67 +1479,67 @@ Do you want to remove the file from the recently used projects -list? Vuoi rimuovere il file dalla lista dei progetti recentemente usati? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1401,26 +1583,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1462,111 +1649,135 @@ Options: ProjectFile + Project File File di progetto + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... + Misra C + 2012 + + 2023 + MISRA rule texts + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: Percorsi: + + Add... Aggiungi... @@ -1574,6 +1785,9 @@ Options: + + + Edit Modifica @@ -1582,269 +1796,327 @@ Options: + + + + Remove Rimuovi + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Types and Functions + + Analysis + This is a workfolder that Cppcheck will use for various purposes. + Parser + Cppcheck (built in) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + Check that each class has a safe public interface + Limit analysis + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) + Max CTU depth + Enable inline suppressions Abilita le soppressioni + 2025 + Misra C++ + 2008 + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting + External tools + Up Su + Down Giù + Platform + Clang (experimental) + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + Max recursion in template instantiation + Warning options + Root path: + Filepaths in warnings will be relative to this path + Warning tags (separated by semicolon) + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + Libraries + Suppressions + Add + + Addons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 + Thread safety + Coding standards + Cert C++ + Bug hunting (Premium) + Defines: Definizioni: @@ -2464,21 +2736,25 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. + Results Risultati + Critical errors + Analysis Log + Warning Details @@ -2487,26 +2763,31 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza.ScratchPad + Scratchpad Blocchetto per appunti + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename Nome file + Check Scansiona @@ -2515,21 +2796,25 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza.Settings + Preferences Preferenze + General Generale + Add... Aggiungi... + Number of threads: Numero di threads: @@ -2539,56 +2824,67 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. + Force checking all #ifdef configurations Forza la scansione di tutte le configurazioni #ifdef + Show full path of files Mostra tutto il percorso dei files + Show "No errors found" message when no errors found Mostra il messaggio "Nessun errore trovato" quando nessun errore è stato trovato + Display error Id in column "Id" Mostra l'id dell'errore nella colonna "Id" + Enable inline suppressions Abilita le soppressioni + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2596,117 +2892,143 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove Rimuovi + Max count: + Applications Applicazioni + + Edit... Modifica... + Set as default Imposta come predefinito + Reports Rapporti + Save all errors when creating report Salva tutti gli errori quando viene creato il rapporto + Save full path to files in reports Salva tutto il percorso ai files nei rapporti + Language Lingua @@ -2763,6 +3085,8 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. + + Statistics @@ -2770,113 +3094,135 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. + Project Progetto + Project: Progetto: + Paths: Percorsi: + Include paths: Percorsi di inclusione: + Defines: Definizioni: + Undefines: + Previous Scan Precedente Scansione + Path Selected: Selezionato percorso: + Number of Files Scanned: Numero di Files Scansionati: + Scan Duration: Durata della scansione: + Errors: Errori: + Warnings: Avvisi: + Stylistic warnings: Avvisi sullo stile: + Portability warnings: Avvisi sulla portabilità: + Performance issues: Casi sulla performance: + Information messages: Messaggi di informazione: + Active checkers: + Checkers + History + File: + Copy to Clipboard Copia negli Appunti + Pdf Export @@ -3026,6 +3372,7 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza.ThreadDetails + Thread Details diff --git a/gui/cppcheck_ja.ts b/gui/cppcheck_ja.ts index dcbe8418942..75b2bad9985 100644 --- a/gui/cppcheck_ja.ts +++ b/gui/cppcheck_ja.ts @@ -5,38 +5,45 @@ About + About Cppcheck cppcheckについて + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. CppcheckはC/C++ 静的コード解析ツールです. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-%1 Cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 本ソフトウェアはGNU General Public License Version3 ライセンスの元で配布されます + Visit Cppcheck homepage at %1 Cppcheckのホームページはこちら %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> <html><head/><body><p>ライブラリの開発者に感謝を捧げます:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> @@ -46,11 +53,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application アプリケーションの追加 + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Parameters: -l(line) (file) + &Name: 表示名(&N): + &Executable: 実行ファイルのパス(&E): + &Parameters: パラメータ(&P): + Browse 参照 @@ -191,16 +204,19 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck GUI ヘルプ + Contents 目次 + Index インデックス @@ -218,16 +234,19 @@ Parameters: -l(line) (file) LibraryAddFunctionDialog + Add function 関数の追加 + Function name(s) 関数の名称 + Number of arguments 引数の数 @@ -236,86 +255,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor ライブラリエディタ + Open 開く + Save 保存する + Save as 別名で保存する + Functions 関数 + Sort ソート + Add 追加 + Filter: フィルタ: + Comments コメント + noreturn noreturn(返り値なし) + False False(偽) + True True(真) + Unknown Unknown(不明) + return value must be used 返り値は使用されなければならない + ignore function in leaks checking リークの解析中に無視する関数 + Arguments Arguments(引数) + Edit 編集 @@ -361,11 +397,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument 引数の編集 + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -381,12 +419,14 @@ Parameters: -l(line) (file) + Not bool 非ブール型 非bool値 + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -402,78 +442,99 @@ Parameters: -l(line) (file) + Not null 非NULL + Not uninit 未初期化 + String 文字列 + Format string フォーマット文字列 + Min size of buffer バッファの最小サイズ + + Type Type(型) + + None None(無) + + argvalue argvalue(引数の値) + + mul mul(積) + + strlen strlen(文字数) + + Arg Arg(引数) + + Arg2 Arg2(第二引数) + and and(和) + Valid values 妥当な値 @@ -486,542 +547,654 @@ Parameters: -l(line) (file) + &File ファイル(&F) + &View 表示(&V) + &Toolbars ツールバー(&T) + &Help ヘルプ(&H) + C++ standard C++標準 + &C standard C standard &C標準 + &Edit 編集(&E) + Standard 言語規格 + Categories カテゴリ + &License... ライセンス(&L)... + A&uthors... 作者(&u)... + &About... Cppcheckについて(&A)... + &Files... ファイル選択(&F)... + + Analyze files Check files ファイルをチェックする + Ctrl+F Ctrl+F + &Directory... ディレクトリ選択(&D)... + + Analyze directory Check directory ディレクトリをチェックする + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop 停止(&S) + + Stop analysis Stop checking チェックを停止する + Esc Esc + &Save results to file... 結果をファイルに保存(&S)... + Ctrl+S Ctrl+S + &Quit 終了(&Q) + &Clear results 結果をクリア(&C) + &Preferences 設定(&P) + + Show style warnings スタイル警告を表示 - + + + Show errors エラーを表示 - - + + Information 情報 + Show information messages 情報メッセージを表示 + Show portability warnings 移植可能性の問題を表示 + Show Cppcheck results Cppcheck結果を表示する + Clang Clang + Show Clang results Clangの結果を表示 + &Filter フィルター(&F) + Filter results フィルタ結果 + Windows 32-bit ANSI Windows 32-bit ANSIエンコード + Windows 32-bit Unicode Windows 32-bit Unicode + Unix 32-bit Unix 32-bit + Unix 64-bit Unix 64-bit + Windows 64-bit Windows 64-bit + &Print... 印刷(&P)... + Print the Current Report 現在のレポートを印刷 + Print Pre&view... 印刷プレビュー(&v)... + Open a Print Preview Dialog for the Current Results 現在のレポートをプレビュー表示 + Open library editor ライブラリエディタを開く + C&lose Project File プロジェクトを閉じる(&l) + &Edit Project File... プロジェクトの編集(&E)... + &Statistics 統計情報(&S) - + + + Show warnings 警告を表示 + + Show performance warnings パフォーマンス警告を表示 + Show &hidden 非表示を表示(&h) + &Check all すべてのエラーを表示(&C) + Checking for updates 更新の確認 + Hide 非表示 + + Report レポート + A&nalyze チェック(&n) + Filter フィルター + &Reanalyze modified files &Recheck modified files 変更ありファイルを再解析(&R) + Reanal&yze all files 全ファイル再解析(&y) + Ctrl+Q Ctrl+Q + Style war&nings スタイル警告(&n) + E&rrors エラー(&r) + &Uncheck all すべてのエラーを非表示(&U) + Collapse &all ツリーを折り畳む(&a) + &Expand all ツリーを展開(&E) + &Standard 言語規格(&S) + Standard items 標準項目 + &Contents コンテンツ(&C) + Open the help contents ヘルプファイルを開く + F1 F1 + Toolbar ツールバー + &Categories カテゴリ(&C) + Error categories エラーカテゴリ + &Open XML... XMLを開く(&O)... + Open P&roject File... プロジェクトを開く(&R)... + Ctrl+Shift+O Ctrl+Shift+O + Sh&ow Scratchpad... スクラッチパッドを表示(&o)... + &New Project File... 新規プロジェクト(&N)... + Ctrl+Shift+N Ctrl+Shift+N + &Log View ログを表示(&L) + Log View ログ表示 + &Warnings 警告(&W) + Per&formance warnings パフォーマンス警告(&f) + &Information 情報(&I) + &Portability 移植可能性(&P) + P&latforms プラットフォーム(&l) + C++&11 C++11(&1) + C&99 C99(&9) + &Posix Posix(&P) + C&11 C11(&1) + &C89 C89(&C) + &C++03 C++03(&C) + &Library Editor... ライブラリエディタ(&L)... + &Auto-detect language 自動言語検出(&A) + &Enforce C++ C++ 強制(&E) + E&nforce C C 強制(&n) + C++14 C++14 + Reanalyze and check library ライブラリを再チェックする + Check configuration (defines, includes) チェックの設定(define、インクルード) + C++17 C++17 + C++20 C++20 + Compliance report... コンプライアンスレポート... + Normal ノーマル @@ -1047,26 +1220,30 @@ Parameters: -l(line) (file) + Autosar AUTOSAR + EULA... EULA... + Thread Details スレッド詳細 + Show thread details スレッド詳細を表示 - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1075,23 +1252,28 @@ This is probably because the settings were changed between the Cppcheck versions Cppcheckの古いバージョンの設定には互換性がありません。エディタアプリケーションの設定を確認して修正してください、そうしないと正しく起動できないかもしれません。 - + You must close the project file before selecting new files or directories! 新しいファイル/ディレクトリをチェックするには現在のプロジェクトを閉じてください! - - + + Quick Filter: クイックフィルタ: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration コンフィグレーションの選択 - + Found project file: %1 Do you want to load this project file instead? @@ -1100,64 +1282,64 @@ Do you want to load this project file instead? 現在のプロジェクトの代わりにこのプロジェクトファイルを読み込んでもかまいませんか? - + The library '%1' contains unknown elements: %2 このライブラリ '%1' には次の不明な要素が含まれています。 %2 - + File not found ファイルがありません - + Bad XML 不正なXML - + Missing attribute 属性がありません - + Bad attribute value 不正な属性があります - + Unsupported format サポートされていないフォーマット - + Duplicate platform type プラットフォームの種類が重複しています - + Platform type redefined プラットフォームの種類が再定義されました - + Unknown element 不明な要素 - + Failed to load the selected library '%1'. %2 選択したライブラリの読み込みに失敗しました '%1' %2 - - - - + + + + Error エラー @@ -1170,38 +1352,38 @@ Do you want to load this project file instead? %1 - %2 の読み込みに失敗 - - + + XML files (*.xml) XML ファイル (*.xml) - + Open the report file レポートを開く - + License ライセンス - + Authors 作者 - + Save the report file レポートを保存 - + Text files (*.txt) テキストファイル (*.txt) - + CSV files (*.csv) CSV形式ファイル (*.csv) @@ -1210,32 +1392,32 @@ Do you want to load this project file instead? コンプライアンスレポートをすぐに生成できません。解析が完了し成功していなければなりません。コードを再解析して、致命的なエラーがないことを確認してください。 - + Project files (*.cppcheck);;All files(*.*) プロジェクトファイル (*.cppcheck);;すべてのファイル(*.*) - + Select Project File プロジェクトファイルを選択 - + Failed to open file ファイルを開くのに失敗しました - + Unknown project file format プロジェクトファイルの形式が不明です - + Failed to import project file プロジェクトファイルのインポートに失敗しました - + Failed to import '%1': %2 Analysis is stopped. @@ -1244,40 +1426,40 @@ Analysis is stopped. 解析を停止しました。 - + Failed to import '%1' (%2), analysis is stopped '%1' (%2) のインポートに失敗しました。解析は停止 - + Install インストール - + New version available: %1. %2 新しいバージョンが利用可能です。: %1. %2 - - - - + + + + Project: プロジェクト: - + No suitable files found to analyze! チェック対象のファイルがみつかりません! - + C/C++ Source C/C++のソースコード - + Compile database コンパイルデータベース @@ -1290,22 +1472,22 @@ Analysis is stopped. Borland C++ Builder 6 - + Select files to analyze チェック対象のファイルを選択 - + Select directory to analyze チェックするディレクトリを選択してください - + Select the configuration that will be analyzed チェックの設定を選択 - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1314,37 +1496,37 @@ Do you want to proceed analysis without using any of these project files? - + Duplicate define 重複した定義 - + File not found: '%1' ファイルがありません: '%1' - + Failed to load/setup addon %1: %2 アドオンの読み込みまたは設定に失敗 %1 - %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. %1のロードに失敗しました。あなたの Cppcheck は正しくインストールされていません。あなたは --data-dir=<directory> コマンドラインオプションを使ってこのファイルの場所を指定できます。ただし、この --data-dir はインストールスクリプトによって使用されていなければなりません。またGUI版はこれを使用しません。さらに、全ての設定は調整済みでなければなりません。 - + Failed to load %1 - %2 Analysis is aborted. 読み込みに失敗 %1 - %2 - - + + %1 Analysis is aborted. @@ -1353,7 +1535,7 @@ Analysis is aborted. 解析は中止した。 - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1363,7 +1545,7 @@ Do you want to proceed? 新しくXMLファイルを開くと現在の結果が削除されます。実行しますか? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1372,77 +1554,77 @@ Do you want to stop the analysis and exit Cppcheck? チェックを中断して、Cppcheckを終了しますか? - + About CppCheckについて - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML ファイル (*.xml);;テキストファイル (*.txt);;CSVファイル (*.csv) - + Build dir '%1' does not exist, create it? ビルドディレクトリ'%1'がありません。作成しますか? - + To check the project using addons, you need a build directory. アドオンを使用してプロジェクトをチェックするためには、ビルドディレクトリが必要です。 - + Show Mandatory 必須を表示 - + Show Required 要求を表示 - + Show Advisory 推奨を表示 - + Show Document ドキュメントを表示 - + Show L1 L1を表示 - + Show L2 L2を表示 - + Show L3 L3を表示 - + Show style スタイルを表示 - + Show portability 移植可能性を表示 - + Show performance パフォーマンスを表示 - + Show information 情報を表示 @@ -1451,22 +1633,22 @@ Do you want to stop the analysis and exit Cppcheck? '%1'のインポートに失敗しました。(チェック中断) - + Project files (*.cppcheck) プロジェクトファイル (*.cppcheck) - + Select Project Filename プロジェクトファイル名を選択 - + No project file loaded プロジェクトファイルが読み込まれていません - + The project file %1 @@ -1528,26 +1710,31 @@ Options: NewSuppressionDialog + New suppression 新しい指摘の抑制 + Error ID エラーID + File name ファイル名 + Line number 行数 + Symbol name シンボル名 @@ -1589,28 +1776,33 @@ Options: ProjectFile + Project File プロジェクトファイル + Paths and Defines パスと定義 + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) プロジェクトのインポート(Visual studio / compile database Borland C++ Builder 6)) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' 定義(Define)はセミコロン';'で区切る必要があります。 例: DEF1;DEF2=5;DEF3=int + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. カスタマイズした cfgファイルを同じフォルダにプロジェクトファイルとして保存してください。ここに表示できるようになります。 @@ -1620,48 +1812,59 @@ Options: + MISRA rule texts MISRA ルールテキスト + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>MISRA C 2012 pdfのAppendix A &quot;Summary of guidelines&quot; からテキストをコピーペーストしてください。</p></body></html> + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>選択済み:</p><p> * 全Debug と Release設定をチェックする</p><p> * 最初にマッチした Debug 設定のみチェックする</p><p><br/></p></body></html> + + Browse... 参照... + Analyze all Visual Studio configurations Visual Studioの全ての設定をチェックする + Selected VS Configurations 選択したVS設定 + Paths: パス: + + Add... 追加... @@ -1669,6 +1872,9 @@ Options: + + + Edit 編集 @@ -1677,87 +1883,108 @@ Options: + + + + Remove 取り除く + Undefines: 定義取り消し(Undefines): + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 定義の取り消しはセミコロンで区切ります。例: UNDEF1;UNDEF2;UNDEF3 + Include Paths: インクルードパス: + Include file インクルードファイル + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">ヘッダファイルを強制的にインクルードします</span></p></body></html> + Browse.. ブラウズ.. + Types and Functions 型と関数 + + Analysis チェック + This is a workfolder that Cppcheck will use for various purposes. Cppcheckがさまざまな目的で使用するワークディレクトリ。 + Parser パーサー + Cppcheck (built in) Cppcheckビルトイン + Check level チェックレベル + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. #R 限定的 -- 開発者が直接的に結果を得るための解析を意味します。解析は限定的で高速ですがより少ない結果になります。 + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. #N 通常 -- CIでの通常の解析を意味します。解析が合理的な時間内に完了すべきです。 + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). #E 徹底的 -- ナイトリービルド等を意味します。解析時間はより長くなることがあります (コンパイルの10倍以上時間がかかってもよい)。 + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) ヘッダファイルのコードもチェック (通常はONにしてください、制限するときのみOFF) @@ -1767,194 +1994,234 @@ Options: + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. タグが追加された場合、警告上で右クリックしてそれらのタグの中の一つを設定できます。警告を分類できます。 + Exclude source files 除外するソースファイル + Exclude folder... フォルダで除外... + Exclude file... ファイルで除外... + Check that each class has a safe public interface クラスが安全で公開されたインターフェースをもっているか確認 + Limit analysis 解析の制限 + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) 未使用テンプレートのコードもチェック (解析に時間がかかり、また正確性は低い) + Max CTU depth CTUの最大深さ + Enable inline suppressions inline抑制を有効にする + Misra C MISRA C + 2012 2012 + + 2023 2023 + 2025 2025 + Misra C++ MISRA C++ + 2008 2008 + Cert C++ Cert C++ + Safety profiles (defined in C++ core guidelines) 安全性プロファイル(C++コアガイドラインで定義) + Bug hunting (Premium) バグハンティング(プレミアム) + External tools 外部ツール + Up 上 + Down 下 + Platform プラットフォーム + Clang (experimental) Clang (実験的) + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. 可能な限りクラスが柔軟であり堅牢であることを望む場合、公開されたインターフェースが非常に堅牢です。Cppcheckは引数があらゆる値をとりうると仮定します。 + Max recursion in template instantiation テンプレートインスタンス化の最大再帰回数 + Warning options 警告オプション + Root path: ルートパス: + Filepaths in warnings will be relative to this path 警告中のファイルパスはこのパスからの相対パスになります + Warning tags (separated by semicolon) 警告タグ(セミコロン区切り) + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck ビルドディレクトリ (全プログラムチェック, 差分チェック, 統計等) + Libraries ライブラリ + Suppressions 指摘の抑制 + Add 追加 + + Addons アドオン + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. 注意: アドオンには<a href="https://www.python.org/">Python</a>が必要です。 + Y2038 Y2038 + Thread safety スレッドセーフ + Coding standards コーディング標準 @@ -1964,11 +2231,13 @@ Options: + Cert C CERT C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) CERT-INT35-C: int型の精度 (もしサイズが精度と一致する場合空のままにしてください) @@ -1978,11 +2247,13 @@ Options: + Autosar AUTOSAR + Bug hunting バグハント @@ -1996,6 +2267,7 @@ Options: + Defines: 定義(Defines): @@ -2550,21 +2822,25 @@ Please check the application path and parameters are correct. ResultsView + Results 結果 + Critical errors 致命的なエラー + Analysis Log チェックログ + Warning Details 警告の詳細 @@ -2674,26 +2950,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad スクラッチパッド + Copy or write some C/C++ code here: ここに C/C++のコードをコピーペーストまたは記入してください: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": オプション: ファイル名を入力(言語は自動判定)して"チェック"をクリックしてください: + filename ファイル名 + Check チェック @@ -2702,21 +2983,25 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences 設定 + General 全般 + Add... 追加... + Number of threads: 解析用のスレッド数: @@ -2726,57 +3011,68 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations Check all #ifdef configurations すべての #ifdef をチェックする + Show full path of files ファイルのフルパスを表示 + Show "No errors found" message when no errors found エラーが無いときは"エラーなし"を表示 + Display error Id in column "Id" エラーIDを "Id" に表示する + Enable inline suppressions inline抑制を有効にする + Check for inconclusive errors also 結論のでない指摘もチェックする + Show statistics on check completion チェック完了時に統計情報を表示する + Check for updates 更新の確認 + Show internal warnings in log ログの内部警告も表示する + Addons アドオン + Python binary (leave this empty to use python in the PATH) Pythonインタプリタの場所(空白の場合システムのPATHから検索) @@ -2784,117 +3080,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon MISRAアドオン + MISRA rule texts file MISRA ルールテキストファイル + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdfのテキストをテキストファイルにコピー</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clangの場所(空白の場合システムのPATHから検索) + Visual Studio headers Visual Studioのヘッダ + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Visual Studioのヘッダーファイル(セミコロン区切り';')。</p><p>Visual Studio コマンドプロンプトを開き、 &quot;SET INCLUDE&quot;. と入力後、そのパスをコピーペーストしてください。</p></body></html> + Code Editor コードエディタ + Code Editor Style コードエディタスタイル + System Style システムのデフォルトのスタイル + Default Light Style ライトスタイルをデフォルトに + Default Dark Style ダークスタイルをデフォルトに + Custom カスタム + Remove 削除 + Max count: #M 最大数: + Applications アプリケーション + + Edit... 編集... + Set as default デフォルトとして設定 + Reports レポート + Save all errors when creating report レポート作成時にすべての警告/エラーを保存 + Save full path to files in reports レポートにファイルのフルパスを保存 + Language 言語 @@ -2951,6 +3273,8 @@ To toggle what kind of errors are shown, open view menu. + + Statistics @@ -2958,113 +3282,135 @@ To toggle what kind of errors are shown, open view menu. + Project プロジェクト + Project: プロジェクト: + Paths: パス: + Include paths: インクルードパス: + Defines: 定義(define): + Undefines: 定義取り消し(undef): + Previous Scan 前回の解析 + Path Selected: ディレクトリ選択: + Number of Files Scanned: 解析済みファイル数: + Scan Duration: 解析にかかった時間: + Errors: エラー: + Warnings: 警告: + Stylistic warnings: スタイル警告: + Portability warnings: 移植可能性の警告: + Performance issues: パフォーマンス警告: + Information messages: 情報メッセージ: + Active checkers: 有効なチェッカー: + Checkers チェッカー + History ヒストリー + File: ファイル: + Copy to Clipboard クリップボードにコピー + Pdf Export PDF エクスポート @@ -3214,6 +3560,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details スレッド詳細 diff --git a/gui/cppcheck_ka.ts b/gui/cppcheck_ka.ts index 7464052e694..97fe21d6f96 100644 --- a/gui/cppcheck_ka.ts +++ b/gui/cppcheck_ka.ts @@ -5,27 +5,32 @@ About + About Cppcheck Cppcheck-ის შესახებ + Version %1 ვერსია %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - ხელსაწყო სტატიკური C/C++ კოდის ანალიზისთვის. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-%1 Cppcheck-ის გუნდი. ყველა უფლება დაცულია. + This program is licensed under the terms of the GNU General Public License version 3 ვრცელდება GNU-ის საჯარო ლიცენზიის, @@ -33,11 +38,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 ეწვიეთ Cppcheck-ის ვებგვერდს მისამართზე %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> <html><head/><body><p>დიდი მადლობა ამ ბიბლიოთეკებს, რომლებსაც ვიყენებთ::</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application აპლიკაციის დამატება + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -67,21 +76,25 @@ Parameters: -l(line) (file) + &Name: &სახელი: + &Executable: &გამშვები ფაილი: + &Parameters: &პარამეტრები: + Browse დათვალიერება @@ -179,16 +192,19 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck-ის გრაფიკული ინტერფეისის დახმარება + Contents შემცველობა + Index ინდექსი @@ -206,16 +222,19 @@ Parameters: -l(line) (file) LibraryAddFunctionDialog + Add function ფუნქციის დამატება + Function name(s) ფუნქციის სახელ(ებ)-ი + Number of arguments არგუმენტების რაოდენობა @@ -224,86 +243,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor ბიბლიოთეკის რედაქტორი + Open გახსნა + Save შენახვა + Save as შენახვა, როგორც + Functions ფუნქციები + Sort დალაგება + Add დამატება + Filter: ფილტრი: + Comments კომენტარები + noreturn noreturn + False მცდარი + True სიმართლე + Unknown უცნობი + return value must be used უნდა გამოიყენოთ დაბრუნებული მნიშვნელობა + ignore function in leaks checking ფუნქციების გამოტოვება გაჟონვები შემოწმებისას + Arguments არგუმენტები + Edit ჩასწორება @@ -348,11 +384,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument არგუმენტის ჩასწორება + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -363,11 +401,13 @@ Parameters: -l(line) (file) + Not bool ლოგიკური მნ. არაა + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -378,78 +418,99 @@ Parameters: -l(line) (file) + Not null ნულოვანი არაა + Not uninit Uninit არაა + String სტრიქონი + Format string სტრიქონის ფორმატი + Min size of buffer ბუფერის მინ. ზომა + + Type ტიპი + + None არაფერი + + argvalue argvalue + + mul mul + + strlen strlen + + Arg არგ + + Arg2 Arg2 + and და + Valid values სწორი მნიშვნელობები @@ -462,511 +523,616 @@ Parameters: -l(line) (file) + A&nalyze ა&ნალიზი + Standard სტანდარტული + &File &ფაილი + &View &ხედი + &Toolbars &ხელსაწყოთა ზოლები + C++ standard C++-ის სტანდარტი + &C standard C standard &C-ის სტანდარტი + &Edit &ჩასწორება + &License... &ლიცენზია... + A&uthors... &ავტორები... + &About... &შესახებ... + &Files... ფაილ&ები... + + Analyze files Check files ფაილების ანალიზი + Ctrl+F Ctrl+F + &Directory... &საქაღალდე.... + + Analyze directory Check directory საქაღალდის ანალიზი + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop &გაჩერება + + Stop analysis Stop checking ანალიზის გაჩერება + Esc Esc + &Save results to file... შედეგები&ს შენახვა ფაილში.... + Ctrl+S Ctrl+S + &Quit გამოსვლა + &Clear results შედეგების &გასუფთავება + &Preferences &გამართვა - + + + Show errors შეცდომების ჩვენება - + + + Show warnings გაფრთხილების ჩვენება + + Show performance warnings წარმადობის გაფრთხილებების ჩვენება + Show &hidden დამალულის &ჩვენება - - + + Information ინფორმაცია + Show information messages ინფორმაციის შეტყობინებების ჩვენება + Show portability warnings გადატანადობის გაფრთხილებების ჩვენება + Show Cppcheck results Cppcheck-ის შედეგები ჩვენება + Clang Clang + Show Clang results Clang-ის შედეგები ჩვენება + &Filter &ფილტრი + Filter results შედეგების გაფილტვრა + Windows 32-bit ANSI Windows 32-bit ANSI + Windows 32-bit Unicode Windows 32-bit Unicode + Unix 32-bit Unix 32-bit + Unix 64-bit Unix 64-bit + Windows 64-bit Windows 64-bit + &Print... &ბეჭდვა… + Print the Current Report მიმდინარე ანგარიშის დაბეჭდვა + Print Pre&view... საბეჭდის &გადახედვა... + Open a Print Preview Dialog for the Current Results მიმდინარე შედეგების დაბეჭდვის მინიატურის დიალოგის ჩვენება + Open library editor ბიბლიოთეკის რედაქტორის გახსნა + &Check all &ყველას ჩართვა + Checking for updates განახლებების შემოწმება + Hide დამალვა + + Report + Filter ფილტრი + &Reanalyze modified files &Recheck modified files ყველა შეცვლილი ფაილის თავიდან ანალი&ზი + Reanal&yze all files &ყველა ფაილის თავიდან ანალიზი + Ctrl+Q Ctrl+Q + Style war&nings ს&ტილის გაფრთხილებები + E&rrors &შეცდომები + &Uncheck all ყველას ჩამოყ&რა + Collapse &all ყველას ჩაკეცვ&ა + &Expand all &ყველას ამოკეცვა + &Standard &სტანდარტული + Standard items სტანდარტული ელემენტები + Toolbar ხელსაწყოთა ზოლი + &Categories &კატეგორიები + Error categories შეცდომის კატეგორიები + &Open XML... &XML-ის გახსნა... + Open P&roject File... პ&პროექტის ფაილის გახსნა... + Ctrl+Shift+O Ctrl+Shift+O + Sh&ow Scratchpad... ბლ&ოკნოტის ჩვენება... + &New Project File... &ახალი პროექტის ფაილი... + Ctrl+Shift+N Ctrl+Shift+N + &Log View ჟურნა&ლის ხედი + Log View ჟურნალის ხედი + C&lose Project File პროექტის ფაი&ლის დახურვა + &Edit Project File... პროექტის ფაილის ჩასწორ&ება... + &Statistics &სტატისტიკა + &Warnings &გაფრთხილებები + Per&formance warnings წარმატების გა&ფრთხილებები + &Information &ინფორმაცია + &Portability &გადატანადობა + P&latforms პ&ლატფორმები + C++&11 C++&11 + C&99 C&99 + &Posix &Posix + C&11 C&11 + &C89 &C89 + &C++03 &C++03 + &Library Editor... ბიბ&ლიოთეკის რედაქტორი... + &Auto-detect language ენის &ავტოდადგენა + &Enforce C++ ძ&ალით C++ + E&nforce C ძა&ლით C + C++14 C++14 + Reanalyze and check library ბიბლიოთეკის თავიდან ანალიზი და შემოწმება + Check configuration (defines, includes) კონფიგურაციის შემოწმება (defines, includes) + C++17 C++17 + C++20 C++20 + Compliance report... შესაბამისობის ანგარიში... + Normal ნორმალური @@ -984,68 +1150,84 @@ Parameters: -l(line) (file) + Autosar + EULA... + Thread Details + Show thread details + &Contents &შემცველობა + Categories კატეგორიები + + Show style warnings სტილის გაფრთხილების ჩვენება + Open the help contents დახმარების შემცველობის გახსნა + F1 F1 + &Help &დახმარება - - + + Quick Filter: სწრაფი ფილტრი: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration აირჩიეთ კონფიგურაცია - + Found project file: %1 Do you want to load this project file instead? @@ -1054,61 +1236,61 @@ Do you want to load this project file instead? გნებავთ, სამაგიეროდ, ეს პროექტის ფაილი ჩატვირთოთ? - + File not found ფაილი ნაპოვნი არაა - + Bad XML არასწორი XML - + Missing attribute აკლია ატრიბუტი - + Bad attribute value არასწორი ატრიბუტის მნიშვნელობა - + Unsupported format მხარდაუჭერელი ფორმატი - + Duplicate define გამეორებული აღწერა - + Failed to load the selected library '%1'. %2 ჩავარდა ჩატვირთვა მონიშნული ბიბლიოთეკისთვის '%1'. %2 - + File not found: '%1' ფაილი ვერ ვიპოვე: '%1' - + Failed to load/setup addon %1: %2 დამატების (%1) ჩატვირთვა/მორგება ჩავარდა: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. @@ -1117,8 +1299,8 @@ Analysis is aborted. ანალიზი შეწყდა. - - + + %1 Analysis is aborted. @@ -1127,113 +1309,113 @@ Analysis is aborted. ანალიზი შეწყვეტილია. - + License ლიცენზია - + Authors ავტორები - + Save the report file ანგარიშის ფაილში ჩაწერა - - + + XML files (*.xml) XML ფაილები (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + You must close the project file before selecting new files or directories! ახალი ფაილების ან საქაღალდეების არჩევამდე პრორექტის ფაილი უნდა დახუროთ! - + The library '%1' contains unknown elements: %2 ბიბლიოთეკა '%1' უცნობ ელემენტებს შეიცავს: %2 - + Duplicate platform type გამეორებული პლატფორმის ტიპი - + Platform type redefined პლატფორმის ტიპი თავდან აღიწერა - + Unknown element უცნობი ელემენტი - - - - + + + + Error შეცდომა - + Open the report file ანგარიშის ფაილის გახსნა - + Text files (*.txt) ტექსტური ფაილები (*.txt) - + CSV files (*.csv) CSV ფაილები (*.csv) - + Project files (*.cppcheck);;All files(*.*) პროექტის ფაილები (*.cppcheck);;ყველა ფაილი(*.*) - + Select Project File აირჩიეთ პროექტის ფაილი - - - - + + + + Project: პროექტი: - + No suitable files found to analyze! ანალიზისათვის შესაფერისი ფაილები აღმოჩენილი არაა! - + C/C++ Source C/C++ საწყისი კოდი - + Compile database მონაცემთა ბაზის კომპილაცია @@ -1246,22 +1428,22 @@ This is probably because the settings were changed between the Cppcheck versions Borland C++ Builder 6 - + Select files to analyze აირჩეთ ფაილები ანალიზისთვის - + Select directory to analyze აირჩიეთ საქაღალდე ანალიზისთვის - + Select the configuration that will be analyzed აირჩიეთ კონფიგურაცია ანალიზისთვის - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1270,7 +1452,7 @@ Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1281,7 +1463,7 @@ Do you want to proceed? გნებავთ, გააგრძელოთ? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1290,12 +1472,12 @@ Do you want to stop the analysis and exit Cppcheck? გნებავთ, გააჩეროთ ანალიზი და გახვიდეთ Cppcheck-დან? - + About შესახებ - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML ფაილები (*.xml);;ტექსტური ფაილები (*.txt);;CSV ფაილები (*.csv) @@ -1304,32 +1486,32 @@ Do you want to stop the analysis and exit Cppcheck? შესაბამისობის ანგარიშის გენერაცია ახლა შეუძლებელია, რადგან ჯერ ანალიზი წარმატებით უნდა დასრულდეს. სცადეთ, კოდის ანალიზი თავიდან გაუშვათ და დარწმუნდეთ, რომ კრიტიკული შეცდომები არ არსებობს. - + Build dir '%1' does not exist, create it? აგების საქაღალდე (%1) არ არსებობს. შევქმნა? - + To check the project using addons, you need a build directory. პროექტის დამატებებით შესამოწმებლად აგების საქაღალდე გჭირდებათ. - + Failed to open file ფაილის გახსნის შეცდომა - + Unknown project file format უცნობი პროექტის ფაილის ფორმატი - + Failed to import project file პროექტის ფაილის შემოტანა ჩავარდა - + Failed to import '%1': %2 Analysis is stopped. @@ -1338,27 +1520,27 @@ Analysis is stopped. ანალიზი შეწყდა. - + Failed to import '%1' (%2), analysis is stopped '%1'-ის (%2) შემოტანა ჩავარდა. ანალიზი შეწყდა - + Project files (*.cppcheck) პროექტის ფაილები (*.cppcheck) - + Select Project Filename აირჩიეთ პროექტის ფაილის სახელი - + No project file loaded პროექტის ფაილი ჩატვირთული არაა - + The project file %1 @@ -1375,67 +1557,67 @@ Do you want to remove the file from the recently used projects -list? გნებავთ წაშალოთ ეს ფაილი ახლახან გამოყენებული პროექტების სიიდან? - + Install დაყენება - + New version available: %1. %2 ხელმისაწვდომია ახალი ვერსია: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1492,26 +1674,31 @@ Options: NewSuppressionDialog + New suppression ახალი ჩახშობა + Error ID შეცდომის ID + File name ფაილის სახელი + Line number ხაზის ნომერი + Symbol name სიმბოლოს სახელი @@ -1553,120 +1740,145 @@ Options: ProjectFile + Project File პროექტის ფაილი + Paths and Defines ბილიკები და აღწერები + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) პროექტის შემოტანა (Visual studio / მონაცემთა ბაზი კომპილაცია / Borland C++ Builder 6) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' აღწერები წერტილმძიმით უნდა იყოს გამოყოფილი. მაგ: DEF1;DEF2=5;DEF3=int + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. შენიშვნა: ჩადეთ თქვენი .cfg ფაილები იგივე საქაღალდეში, სადაც პროექტის ფაილია. მათ ზემოთ დაინახავთ. + Clang (experimental) Clang (ექსპერიმენტული) + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) თავსართის ფაილებში არსებული კოდის შეწმება (ნაგულისხმევად, ეს ჩართულია. თუ გნებავთ შეზღუდული სწრაფი ანალიზი, მაშინ გამორთეთ ეს) + Max recursion in template instantiation + Filepaths in warnings will be relative to this path ფაილის ბილიკები ამ ბილიკის ფარდობითი იქნება + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. თუ ჭდეები დამატებულია, შეგიძლიათ, გაფრთხილებებზე მარჯვენა ღილაკით დააწკაპუნოთ და დააყენოთ ერთ-ერთი ჭდე. გაფრთხილებების კატეგორიებად დალაგება ხელით შეგიძლიათ. + Exclude source files კოდის ფაილების ამოღება + Exclude folder... საქაღალდის ამოღება... + Exclude file... ფაილის ამოღება... + MISRA rule texts MISRA-ის წესის ტექსტები + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + + Browse... ნუსხა... + Analyze all Visual Studio configurations ყველა Visual Studio-ის კონფიგურაციის ანალიზი + Selected VS Configurations არჩეული VS-ის კონფიგურაციები + Paths: ბილიკები: + + Add... დამატება... @@ -1674,6 +1886,9 @@ Options: + + + Edit ჩასწორება @@ -1682,255 +1897,311 @@ Options: + + + + Remove წაშლა + Undefines: წაშლილი მაკროაღწერები: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 წაშლილი მაკროაღწერები წერტილმძიმით უნდა იყოს გამოყოფილი. მაგ: UNDEF1;UNDEF2;UNDEF3 + Include Paths: ჩასმის ბილიკები: + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Types and Functions ტიპები და ფუნქციები + + Analysis ანალიზი + This is a workfolder that Cppcheck will use for various purposes. ეს სამუშაო საქაღალდეა, რომელსაც Cppcheck სხვადასხვა მიზნებისთვის გამოიყენებს. + Parser ანალიზატორი + Cppcheck (built in) Cppcheck (ჩაშენებული) + Check level შემოწმების დონე + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. ნორმალური -- ნიშნავს ნორმალურ ანალიზს CI-ში. ანალიზი მისაღებ დროში დასრულდება. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). ამომწურავი -- განკუთვნილია ღამის აგებებისთვის და ა.შ. ანალიზის დრო, შეიძლება, უფრო მეტიც იყოს (კომპილაციაზე 10x მეტი დრო ჩვეულებრივი ამბავია). + Check that each class has a safe public interface შემოწმება, აქვს თუ არა ყველა კლასს უსაფრთხო საჯარო ინტერფეისი + Limit analysis ანალიზის შეზღუდვა + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) + Max CTU depth CTU-ის მაქსიმალური სიღრმე + Enable inline suppressions შეცდომების ხაზშივე ჩახშობის ჩართვა + 2025 2025 + Misra C++ Misra C++ + 2008 2008 + Cert C Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) CERT-INT35-C: მთელი რიცხვის სიზუსტე (თუ ზომა სიზუსტეს ემთხვევა, შეგიძლიათ, ცარიელი დატოვოთ) + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting შეცდომებზე ნადირობა + External tools გარე ხელსაწყოები + Up მაღლა + Down ქვემოთ + Platform პლატფორმა + Warning options გაფრთხილების მორგება + Root path: Root ბილიკი: + Warning tags (separated by semicolon) გაფრთხილების ჭდეები (წერტილმძიმით გამოყოფილი) + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck-ის აგების საქაღალდე (მთელი პროგრამის ანალიზი, ინკრემენტული ანალიზი, სტატისტიკა და ა.შ.) + Libraries ბიბლიოთეკები + Suppressions ჩახშობები + Add დამატება + + Addons დამატებები + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. შენიშვნა: დამატებას დაყენებული <a href="https://www.python.org/">Python</a> სჭირდება. + Y2038 Y2038 + Thread safety ნაკადების უსაფრთხოება + Coding standards კოდირების სტანდარტები + Misra C Misra C + 2012 2012 + + 2023 2023 + Cert C++ Cert C++ + Bug hunting (Premium) შეცდომებზე ნადირობა (ფასიანი) @@ -1944,6 +2215,7 @@ Options: + Defines: მაკროაღწრები: @@ -2590,21 +2862,25 @@ To toggle what kind of errors are shown, open view menu. + Results შედეგები + Critical errors კრიტიკული შეცდომები + Analysis Log ანალიზის ჟურნალი + Warning Details გაფრთხილების დეტალები @@ -2613,26 +2889,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad მონახაზი + Copy or write some C/C++ code here: დააკოპირეთ ან დაწერეთ C/C++ კოდი აქ: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": შეიყვანეთ არასავალდებულო ფაილის სახელი (ძირითადად ავტომატური ენის დადგენისთვის) და დააწკაპუნეთ ღილაკზე "შემოწმება": + filename ფაილის სახელი + Check შემოწმება @@ -2641,21 +2922,25 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences მორგება + General ზოგადი + Add... დამატება... + Number of threads: ნაკადების რაოდენობა: @@ -2665,57 +2950,68 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations Check all #ifdef configurations ყველა #ifdef კონფიგურაციის ნაძალადევი შემოწმება + Show full path of files ფაილების სრული ბილიკის ჩვენება + Show "No errors found" message when no errors found "შეცდომები ნაპოვნი არაა" შეტყობინების ჩვენება, როცა შეცდომები აღმოჩენილი არა + Display error Id in column "Id" სვეტში "Id" შეცდომის Id-ის ჩვენება + Enable inline suppressions შეცდომების ხაზშივე ჩახშობის ჩართვა + Check for inconclusive errors also ასევე, ნაჩვენები იქნება საკამათო შეცდომებიც + Show statistics on check completion სტატისტიკის ჩვენება შემოწმების დასრულებისას + Check for updates განახლებების შემოწმება + Show internal warnings in log ჟურნალში შიდა გაფრთხილებების ჩვენება + Addons დამატებები + Python binary (leave this empty to use python in the PATH) ბილიკი python-ის ინტერპრეტატორამდე. (PATH-ში არსებული python-ის გამოსაყენებლად ცარიელი დატოვეთ) @@ -2723,117 +3019,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon MISRA-ის დამატება + MISRA rule texts file MISRA-ის წესის ტექსტების ფაილი + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) ბილიკი Clang-ის ინტერპრეტატორამდე. (PATH-ში არსებულის გამოსაყენებლად ცარიელი დატოვეთ) + Visual Studio headers Visual Studio-ის თავსართები + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor კოდის რედაქტორი + Code Editor Style კოდის რედაქტორის სტილი + System Style სისტემის მიყოლა + Default Light Style ნაგულისხმევი ღია სტილი + Default Dark Style ნაგულისხმევი მუქი სტილი + Custom მომხმარებლის + Remove წაშლა + Max count: + Applications აპლიკაციები + + Edit... რედაქტირება... + Set as default ნაგულისხმებად დაყენება + Reports ანგარიში + Save all errors when creating report ყველა შეცდომის შენახვა ანგარიშის შექმნისას + Save full path to files in reports ფაილების სრული ბილიკის ჩვენება ანგარიშებში + Language ენა @@ -2890,6 +3212,8 @@ To toggle what kind of errors are shown, open view menu. + + Statistics @@ -2897,113 +3221,135 @@ To toggle what kind of errors are shown, open view menu. + Project პროექტი + Project: პროექტი: + Paths: ბილიკები: + Include paths: თავსართის ფაილის ბილიკები: + Defines: მაკროაღწრები: + Undefines: წაშლილი მაკროაღწერები: + Previous Scan წინა სკანირება + Path Selected: არჩეული ბილიკი: + Number of Files Scanned: დასკანერებულია ფაილების რაოდენობა: + Scan Duration: სკანირების ხანგრძლივობა: + Errors: შეცდომები: + Warnings: გაფრთხილებები: + Stylistic warnings: სტილისტური გაფრთხილებები: + Portability warnings: გადატანადობის გაფრთხილებები: + Performance issues: წარმადობის პრობლემები: + Information messages: ინფორმაციის შეტყობინებები: + Active checkers: აქტიური შემმოწმებლები: + Checkers შაში + History ისტორია + File: ფაილი: + Copy to Clipboard ბუფერში კოპირება + Pdf Export PDF-ად გატანა @@ -3153,6 +3499,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details diff --git a/gui/cppcheck_ko.ts b/gui/cppcheck_ko.ts index a7bae5f31f4..09f564e6380 100644 --- a/gui/cppcheck_ko.ts +++ b/gui/cppcheck_ko.ts @@ -5,21 +5,25 @@ About + About Cppcheck Cppcheck 정보 + Version %1 버전 %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - 정적 C/C++ 코드 분석 도구. + This program is licensed under the terms of the GNU General Public License version 3 이 프로그램은 GNU General Public License version 3을 @@ -27,17 +31,20 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 Cppcheck 홈페이지(%1)를 방문해보세요 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application 응용 프로그램 추가 + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: + &Name: 이름(&N): + &Executable: 실행 파일(&E): + &Parameters: 명령행 인자(&P): + Browse 찾기 @@ -136,16 +149,19 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: HelpDialog + Cppcheck GUI help + Contents + Index @@ -163,16 +179,19 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -181,66 +200,79 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: LibraryDialog + Library Editor + Open + Save + Functions + Add + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit 편집 @@ -257,21 +289,25 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: + Sort + Filter: + Comments + Save as @@ -304,11 +340,13 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -319,11 +357,13 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -334,78 +374,99 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -418,355 +479,426 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: + Checking for updates + Hide 숨기기 + &File 파일(&F) + &View 보기(&V) + &Toolbars 도구바(&T) + + Report + &Help 도움말(&H) + &Edit 편집(&E) + Standard 표준 도구 + Categories 분류 도구 + Filter 필터 도구 + &License... 저작권(&L)... + A&uthors... 제작자(&u)... + &About... 정보(&A)... + &Files... 파일(&F)... + Ctrl+F Ctrl+F + &Directory... 디렉토리(&D)... + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop 중지(&S) + Esc Esc + &Save results to file... 결과를 파일에 저장(&S)... + Ctrl+S Ctrl+S + &Quit 종료(&Q) + &Clear results 결과 지우기(&C) + &Preferences 설정(&P) + + Show style warnings 스타일 경고 표시 - + + + Show errors 애러 표시 + &Check all 전체 선택(&C) + &Uncheck all 전체 해제(&U) + Collapse &all 전체 접기(&A) + &Expand all 전체 펼치기(&E) + &Standard 표준 도구(&S) + Standard items 표준 아이템 + &Contents 내용(&C) + Open the help contents 도움말을 엽니다 + F1 F1 + Toolbar 도구바 + &Categories 분류 도구(&C) + Error categories 에러 종류 + &Open XML... XML 열기(&O)... + Open P&roject File... 프로젝트 파일 열기(&R)... + &New Project File... 새 프로젝트 파일(&N)... + &Log View 로그 보기(&L) + Log View 로그 보기 + C&lose Project File 프로젝트 파일 닫기(&L) + &Edit Project File... 프로젝트 파일 편집(&E)... + &Statistics 통계 보기(&S) - + + + Show warnings 경고 표시 + + Show performance warnings 성능 경고 표시 + Show &hidden 숨기기 보기(&H) + Compliance report... + Normal + Autosar + EULA... + Thread Details + Show thread details - - + + Information 정보 + Show information messages 정보 표시 + Show portability warnings 이식성 경고 표시 + &Filter 필터 도구(&F) + Filter results 필터링 결과 + Windows 32-bit ANSI Windows 32-bit ANSI + Windows 32-bit Unicode Windows 32-bit Unicode + Unix 32-bit Unix 32-bit + Unix 64-bit Unix 64-bit + Windows 64-bit Windows 64-bit - - + + Quick Filter: 빠른 필터: - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -775,12 +907,17 @@ This is probably because the settings were changed between the Cppcheck versions Cppcheck 버전간 설정 방법 차이때문인 것으로 보입니다. 편집기 설정을 검사(및 수정)해주세요, 그렇지 않으면 편집기가 제대로 시작하지 않습니다. - + + None of the selected files is part of the project, nothing to analyze. + + + + You must close the project file before selecting new files or directories! 새로운 파일이나 디렉토리를 선택하기 전에 프로젝트 파일을 닫으세요! - + Found project file: %1 Do you want to load this project file instead? @@ -789,210 +926,210 @@ Do you want to load this project file instead? 이 프로젝트 파일을 불러오겠습니까? - - + + XML files (*.xml) XML 파일 (*.xml) - + Open the report file 보고서 파일 열기 - + License 저작권 - + Authors 제작자 - + Save the report file 보고서 파일 저장 - + Text files (*.txt) 텍스트 파일 (*.txt) - + CSV files (*.csv) CSV 파일 (*.csv) - + Project files (*.cppcheck);;All files(*.*) 프로젝트 파일 (*.cppcheck);;모든 파일(*.*) - + Select Project File 프로젝트 파일 선택 - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information - - - - + + + + Project: 프로젝트: - + Duplicate define - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + About - + To check the project using addons, you need a build directory. - + Select Project Filename 프로젝트 파일이름 선택 - + No project file loaded 프로젝트 파일 불러오기 실패 - + The project file %1 @@ -1015,96 +1152,102 @@ Do you want to remove the file from the recently used projects -list? + C++ standard - - - - + + + + Error - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Failed to load the selected library '%1'. %2 - + Unsupported format - + The library '%1' contains unknown elements: %2 - + Duplicate platform type - + Platform type redefined + &Print... + Print the Current Report + Print Pre&view... + Open a Print Preview Dialog for the Current Results + Open library editor - + Unknown element - + Select configuration @@ -1127,67 +1270,74 @@ Options: - + Build dir '%1' does not exist, create it? + + Analyze files + + Analyze directory + &Reanalyze modified files + + Stop analysis - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - + No suitable files found to analyze! - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1195,181 +1345,213 @@ Do you want to stop the analysis and exit Cppcheck? + A&nalyze + &C standard + Reanal&yze all files + Ctrl+Q + Style war&nings + E&rrors + Ctrl+Shift+O + Sh&ow Scratchpad... + Ctrl+Shift+N + &Warnings + Per&formance warnings + &Information + &Portability + Show Cppcheck results + Clang + Show Clang results + P&latforms + C++&11 + C&99 + &Posix + C&11 + &C89 + &C++03 + &Library Editor... + &Auto-detect language + &Enforce C++ + E&nforce C + C++14 C++14 - + Project files (*.cppcheck) + Reanalyze and check library + Check configuration (defines, includes) + C++17 C++17 + C++20 C++20 - + C/C++ Source - + Compile database - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1381,26 +1563,31 @@ Do you want to proceed? NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1442,22 +1629,27 @@ Do you want to proceed? ProjectFile + Project File 프로젝트 파일 + Defines: Defines: + Paths: 경로: + + Add... 추가... @@ -1465,6 +1657,9 @@ Do you want to proceed? + + + Edit 편집 @@ -1473,356 +1668,433 @@ Do you want to proceed? + + + + Remove 제거 + Up 위로 + Down 아래로 + Suppressions + Add + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. + ... + Include Paths: + Paths and Defines + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + Analyze all Visual Studio configurations + Root path: + Warning tags (separated by semicolon) + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + Selected VS Configurations + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Types and Functions + Libraries + Parser + Cppcheck (built in) + Check that each class has a safe public interface + Limit analysis + + Addons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 + Thread safety + Coding standards + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Bug hunting + + Browse... + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int + Platform + This is a workfolder that Cppcheck will use for various purposes. + Clang (experimental) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + Max recursion in template instantiation + Warning options + Filepaths in warnings will be relative to this path + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... + Enable inline suppressions Inline suppression 사용 + Misra C + 2012 + + 2023 + 2025 + MISRA rule texts + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Misra C++ + 2008 + Cert C++ + Safety profiles (defined in C++ core guidelines) + Bug hunting (Premium) + External tools + Import Project (Visual studio / compile database/ Borland C++ Builder 6) + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + + Analysis + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) + Max CTU depth @@ -2339,6 +2611,7 @@ Please check the application path and parameters are correct. ResultsView + Results 결과 @@ -2427,16 +2700,19 @@ To toggle what kind of errors are shown, open view menu. + Critical errors + Analysis Log + Warning Details @@ -2460,26 +2736,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad + filename + Check 검사 + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": @@ -2488,16 +2769,19 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences 설정 + General 일반 + Number of threads: 쓰레드 수: @@ -2507,107 +2791,129 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations 모든 #ifdef 설정을 강제로 검사 + Show full path of files 파일의 전체 경로 표시 + Show "No errors found" message when no errors found 에러가 발견되지 않는 경우 "에러가 없습니다." 메시지 표시 + Enable inline suppressions Inline suppression 사용 + Check for updates + Add... 추가... + Remove 제거 + Applications 응용 프로그램 + + Edit... 편집... + Set as default 기본으로 지정 + Reports 보고서 + Save all errors when creating report 보고서 생성 시 모든 에러 저장 + Save full path to files in reports 보고서에 파일의 전체 경로 저장 + Language 언어 + Display error Id in column "Id" + Check for inconclusive errors also + Show internal warnings in log + Show statistics on check completion + Max count: + Addons + Python binary (leave this empty to use python in the PATH) @@ -2615,71 +2921,87 @@ To toggle what kind of errors are shown, open view menu. + + + ... + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Code Editor + Code Editor Style + Default Light Style + Default Dark Style + Custom + System Style @@ -2736,6 +3058,8 @@ To toggle what kind of errors are shown, open view menu. + + Statistics @@ -2743,93 +3067,111 @@ To toggle what kind of errors are shown, open view menu. + Project 프로젝트 + Project: 프로젝트: + Paths: 경로: + Include paths: Include 경로: + Defines: Defines: + Previous Scan 직전 검사 + Path Selected: 선택된 경로: + Number of Files Scanned: 검사된 파일 수: + Scan Duration: 검사 시간: + Errors: 에러: + Warnings: 경고: + Stylistic warnings: 스타일 경고: + Portability warnings: 이식성 경고: + Performance issues: 성능 경고: + Information messages: 정보 메시지: + Active checkers: + Checkers + Copy to Clipboard 클립보드에 복사 @@ -2956,6 +3298,7 @@ To toggle what kind of errors are shown, open view menu. + Pdf Export @@ -2966,11 +3309,13 @@ To toggle what kind of errors are shown, open view menu. + History + File: @@ -2986,6 +3331,7 @@ To toggle what kind of errors are shown, open view menu. + Undefines: @@ -2999,6 +3345,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details diff --git a/gui/cppcheck_nl.ts b/gui/cppcheck_nl.ts index 0c390ff245c..3bab077e6e3 100644 --- a/gui/cppcheck_nl.ts +++ b/gui/cppcheck_nl.ts @@ -5,27 +5,32 @@ About + About Cppcheck Over Cppcheck + Version %1 Versie %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Een tool voor statische C/C++ code analyse. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 het cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 Dit programma is beschikbaar onder te termen @@ -33,11 +38,13 @@ van de GNU General Public License versie 3 + Visit Cppcheck homepage at %1 Bezoek de Cppcheck homepage op %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ van de GNU General Public License versie 3 ApplicationDialog + Add an application Voeg een nieuwe applicatie toe + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Parameters: -l(lijn) (bestand) + &Name: &Naam: + &Executable: &Uitvoerbaar: + &Parameters: + Browse Bladeren @@ -138,16 +151,19 @@ Parameters: -l(lijn) (bestand) HelpDialog + Cppcheck GUI help + Contents + Index @@ -165,16 +181,19 @@ Parameters: -l(lijn) (bestand) LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -183,86 +202,103 @@ Parameters: -l(lijn) (bestand) LibraryDialog + Library Editor + Open + Save Opslaan + Save as + Functions + Sort + Add + Filter: + Comments + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit Bewerk @@ -308,11 +344,13 @@ Parameters: -l(lijn) (bestand) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -323,11 +361,13 @@ Parameters: -l(lijn) (bestand) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -338,78 +378,99 @@ Parameters: -l(lijn) (bestand) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -422,578 +483,694 @@ Parameters: -l(lijn) (bestand) + A&nalyze + Standard Standaard + &File &Bestand + &View &Weergave + &Toolbars &Werkbalken + C++ standard C++standaard + &C standard C standard C standaard + &Edit Be&werken + &License... &Licentie... + A&uthors... A&uteurs... + &About... &Over... + &Files... &Bestanden... + + Analyze files Check files Controleer bestanden + Ctrl+F Ctrl+F + &Directory... &Mappen... + + Analyze directory Check directory Controleer Map + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop &Stop + + Stop analysis Stop checking Stop controle + Esc Esc + &Save results to file... &Resultaten opslaan... + Ctrl+S Ctrl+S + &Quit &Afsluiten + &Clear results &Resultaten wissen + &Preferences &Voorkeuren - + + + Show errors Toon fouten - + + + Show warnings Toon waarschuwingen + + Show performance warnings Toon presentatie waarschuwingen + Show &hidden Toon &verborgen - - + + Information Informatie + Show information messages Toon informatie bericht + Show portability warnings Toon portabiliteit waarschuwingen + Show Cppcheck results + Clang + Show Clang results + &Filter &Filter + Filter results Filter resultaten + Windows 32-bit ANSI Windows 32-bit ANSI + Windows 32-bit Unicode + Unix 32-bit + Unix 64-bit + Windows 64-bit + &Print... + Print the Current Report + Print Pre&view... + Open a Print Preview Dialog for the Current Results + Open library editor + &Check all &Controleer alles + Checking for updates + Hide Verberg + + Report + Filter + &Reanalyze modified files &Recheck modified files + Reanal&yze all files + Ctrl+Q + Style war&nings + E&rrors + &Uncheck all Selecteer &niets + Collapse &all Alles Inkl&appen + &Expand all Alles &Uitklappen + &Standard &Standaard + Standard items Standaard items + Toolbar Werkbalk + &Categories &Categorieën + Error categories Foute Categorieën + &Open XML... + Open P&roject File... Open P&oject bestand... + Ctrl+Shift+O + Sh&ow Scratchpad... + &New Project File... &Nieuw Project Bestand... + Ctrl+Shift+N + &Log View &Log weergave + Log View Log weergave + C&lose Project File &Sluit Project Bestand + &Edit Project File... &Bewerk Project Bestand... + &Statistics &Statistieken + &Warnings + Per&formance warnings + &Information + &Portability + P&latforms + C++&11 + C&99 + &Posix + C&11 + &C89 + &C++03 + &Library Editor... + &Auto-detect language + &Enforce C++ + E&nforce C + C++14 + Reanalyze and check library + Check configuration (defines, includes) + C++17 + C++20 + Compliance report... + Normal + Autosar + EULA... + Thread Details + Show thread details + &Contents &Inhoud + Categories Categorieën + + Show style warnings Toon stijl waarschuwingen + Open the help contents Open de help inhoud + F1 + &Help &Help - - + + Quick Filter: Snel Filter: - + Select configuration - + Found project file: %1 Do you want to load this project file instead? @@ -1001,91 +1178,91 @@ Do you want to load this project file instead? Wilt u dit project laden in plaats van? - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Licentie - + Authors Auteurs - + Save the report file Rapport opslaan - - + + XML files (*.xml) XML bestanden (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1094,116 +1271,121 @@ This is probably because the settings were changed between the Cppcheck versions Dit is waarschijnlijk omdat de instellingen zijn gewijzigd tussen de versies van cppcheck. Controleer (en maak) de bewerker instellingen, anders zal de bewerker niet correct starten. - + You must close the project file before selecting new files or directories! Je moet project bestanden sluiten voordat je nieuwe bestanden of mappen selekteerd! - + The library '%1' contains unknown elements: %2 - + Unsupported format - + Duplicate platform type - + Platform type redefined - + Unknown element - - - - + + + + Error - + Open the report file Open het rapport bestand - + Text files (*.txt) Tekst bestanden (*.txt) - + CSV files (*.csv) CSV bestanden (*.csv) - + Project files (*.cppcheck);;All files(*.*) Project bestanden (*.cppcheck);;Alle bestanden(*.*) - + Select Project File Selecteer project bestand - - - - + + + + Project: Project: - + + None of the selected files is part of the project, nothing to analyze. + + + + No suitable files found to analyze! - + C/C++ Source - + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1211,76 +1393,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename Selecteer project bestandsnaam - + No project file loaded Geen project bestand geladen - + The project file %1 @@ -1296,67 +1478,67 @@ Kan niet worden gevonden! Wilt u het bestand van de onlangs gebruikte project verwijderen -lijst? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1400,26 +1582,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1441,111 +1628,135 @@ Options: ProjectFile + Project File Project Bestand + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... + Misra C + 2012 + + 2023 + MISRA rule texts + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: Paden: + + Add... Toevoegen... @@ -1553,6 +1764,9 @@ Options: + + + Edit Bewerk @@ -1561,269 +1775,327 @@ Options: + + + + Remove Verwijder + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Types and Functions + + Analysis + This is a workfolder that Cppcheck will use for various purposes. + Parser + Cppcheck (built in) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + Check that each class has a safe public interface + Limit analysis + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) + Max CTU depth + Enable inline suppressions Schakel inline suppressies in + 2025 + Misra C++ + 2008 + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting + External tools + Up Omhoog + Down Omlaag + Platform + Clang (experimental) + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + Max recursion in template instantiation + Warning options + Root path: + Filepaths in warnings will be relative to this path + Warning tags (separated by semicolon) + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + Libraries + Suppressions + Add + + Addons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 + Thread safety + Coding standards + Cert C++ + Bug hunting (Premium) + Defines: Omschrijft: @@ -2445,21 +2717,25 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. + Results Resultaten + Critical errors + Analysis Log + Warning Details @@ -2468,26 +2744,31 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden.ScratchPad + Scratchpad Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename bestandsnaam + Check Controleer @@ -2496,21 +2777,25 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden.Settings + Preferences Instellingen + General Algemeen + Add... Toevoegen... + Number of threads: Aantal threads: @@ -2520,57 +2805,68 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. + Force checking all #ifdef configurations Check all #ifdef configurations Controleer alle #ifdef combinaties + Show full path of files Toon het volledige pad van bestanden + Show "No errors found" message when no errors found Toon "Geen fouten gevonden" indien geen fouten gevonden werden + Display error Id in column "Id" Toon fout ld in kolom "Id" + Enable inline suppressions Schakel inline suppressies in + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2578,117 +2874,143 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove Verwijder + Max count: + Applications Applicaties + + Edit... Bewerk... + Set as default Instellen als standaard + Reports Rapporten + Save all errors when creating report Alle fouten opslaan + Save full path to files in reports Volledig pad opslaan + Language Taal @@ -2741,6 +3063,8 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. + + Statistics @@ -2748,113 +3072,135 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. + Project Project + Project: Project: + Paths: Paden: + Include paths: Bevat paden: + Defines: Omschrijft: + Undefines: + Previous Scan Vorige scan + Path Selected: Pad Geselekteerd: + Number of Files Scanned: Aantal bestanden gescanned: + Scan Duration: Scan tijd: + Errors: Fouten: + Warnings: Waarschuwingen: + Stylistic warnings: Stilistisch waarschuwingen: + Portability warnings: Portabiliteit waarschuwingen: + Performance issues: Presentatie problemen: + Information messages: Informatie bericht: + Active checkers: + Checkers + History + File: + Copy to Clipboard Kopieer naar Clipbord + Pdf Export @@ -3004,6 +3350,7 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden.ThreadDetails + Thread Details diff --git a/gui/cppcheck_ru.ts b/gui/cppcheck_ru.ts index 2eec05aa1b6..d9a1a27c0a2 100644 --- a/gui/cppcheck_ru.ts +++ b/gui/cppcheck_ru.ts @@ -5,27 +5,32 @@ About + About Cppcheck О Cppcheck + Version %1 Версия %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - программа для статического анализа кода на языках С/С++. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 Cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 Эта программа распространяется на @@ -33,11 +38,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 Посетите домашнюю страницу: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application Добавление приложения + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Parameters: -l(line) (file) + &Name: &Название: + &Executable: &Программа: + &Parameters: &Параметры: + Browse Просмотреть @@ -138,16 +151,19 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index @@ -165,16 +181,19 @@ Parameters: -l(line) (file) LibraryAddFunctionDialog + Add function Добавить функцию + Function name(s) Имя(имена) функции + Number of arguments Количество аргументов @@ -183,86 +202,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor Редактор библиотек + Open Открыть + Save Сохранить + Save as Сохранить как + Functions Функции + Sort Сортировать + Add Добавить + Filter: Фильтр: + Comments Комментарии + noreturn + False + True + Unknown + return value must be used должно быть использовано возвращаемое значение + ignore function in leaks checking пропускать функцию при проверке на утечки + Arguments Аргументы + Edit Изменить @@ -308,11 +344,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument Редактировать аргумент + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -323,11 +361,13 @@ Parameters: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -338,78 +378,99 @@ Parameters: -l(line) (file) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -422,578 +483,699 @@ Parameters: -l(line) (file) + A&nalyze Анализ + Standard Стандартные + &File &Файл + &View &Вид + &Toolbars &Панель инструментов + C++ standard Стандарт C++ + &C standard C standard &Стандарт C + &Edit &Правка + &License... &Лицензия... + A&uthors... &Авторы... + &About... &О программе... + &Files... &Файлы... + + Analyze files Check files Проверить файлы + Ctrl+F Ctrl+F + &Directory... &Каталог... + + Analyze directory Check directory Проверка каталога + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop Остановить + + Stop analysis Stop checking Остановить проверку + Esc Esc + &Save results to file... Сохранить отчёт в файл... + Ctrl+S Ctrl+S + &Quit Выход + &Clear results Очистить отчёт + &Preferences Параметры - + + + Show errors Показать ошибки - + + + Show warnings Показать предупреждения + + Show performance warnings Показать предупреждения производительности + Show &hidden Показать скрытые - - + + Information Информационные сообщения + Show information messages Показать информационные сообщения + Show portability warnings Показать предупреждения переносимости + Show Cppcheck results Просмотр результатов Cppcheck + Clang Clang + Show Clang results Просмотр результатов Clang + &Filter Фильтр + Filter results Результаты фильтрации + Windows 32-bit ANSI Windows 32-bit ANSI + Windows 32-bit Unicode Windows 32-bit Unicode + Unix 32-bit Unix 32-bit + Unix 64-bit Unix 64-bit + Windows 64-bit Windows 64-bit + &Print... Печать... + Print the Current Report Напечатать текущий отчет + Print Pre&view... Предварительный просмотр... + Open a Print Preview Dialog for the Current Results Открыть диалог печати для текущих результатов + Open library editor Открыть редактор библиотек + &Check all Отметить все + Checking for updates + Hide Скрыть + + Report + Filter Фильтр + &Reanalyze modified files &Recheck modified files Заново проверить измененные файлы + Reanal&yze all files Заново проверить все файлы + Ctrl+Q + Style war&nings Стилистические предупреждения + E&rrors Ошибки + &Uncheck all Сбросить все + Collapse &all Свернуть все + &Expand all Развернуть все + &Standard Стандартные + Standard items Стандартные элементы + Toolbar Панель инструментов + &Categories Категории + Error categories Категории ошибок + &Open XML... &Открыть XML... + Open P&roject File... Открыть файл &проекта... + Ctrl+Shift+O + Sh&ow Scratchpad... Показать Блокнот + &New Project File... &Новый файл проекта... + Ctrl+Shift+N + &Log View Посмотреть &лог + Log View Посмотреть лог + C&lose Project File &Закрыть файл проекта + &Edit Project File... &Изменить файл проекта... + &Statistics &Статистика + &Warnings Предупреждения + Per&formance warnings Предупреждения производительности + &Information Информационные предупреждения + &Portability Предупреждения переносимости + P&latforms Платформы + C++&11 + C&99 + &Posix + C&11 + &C89 + &C++03 + &Library Editor... Редактор библиотеки + &Auto-detect language Автоматическое определение языка + &Enforce C++ Принудительно C++ + E&nforce C Принудительно C + C++14 C++14 + Reanalyze and check library Повторный анализ библиотеки + Check configuration (defines, includes) Проверить конфигурацию (defines, includes) + C++17 C++17 + C++20 C++20 + Compliance report... + Normal + Autosar + EULA... + Thread Details + Show thread details + &Contents Помощь + Categories Категории + + Show style warnings Показать стилистические предупреждения + Open the help contents Открыть помощь + F1 F1 + &Help Помощь - - + + Quick Filter: Быстрый фильтр: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration Выбор конфигурации - + Found project file: %1 Do you want to load this project file instead? @@ -1002,97 +1184,97 @@ Do you want to load this project file instead? Вы хотите загрузить этот проект? - + File not found Файл не найден - + Bad XML Некорректный XML - + Missing attribute Пропущен атрибут - + Bad attribute value Некорректное значение атрибута - + Unsupported format Неподдерживаемый формат - + Duplicate define - + Failed to load the selected library '%1'. %2 Не удалось загрузить выбранную библиотеку '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Лицензия - + Authors Авторы - + Save the report file Сохранить файл с отчетом - - + + XML files (*.xml) XML-файлы (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1101,37 +1283,37 @@ This is probably because the settings were changed between the Cppcheck versions Возможно, это связано с изменениями в версии программы. Пожалуйста, проверьте (и исправьте) настройки приложения. - + You must close the project file before selecting new files or directories! Вы должны закрыть проект перед выбором новых файлов или каталогов! - + The library '%1' contains unknown elements: %2 Библиотека '%1' содержит неизвестные элементы: %2 - + Duplicate platform type Дубликат типа платформы - + Platform type redefined Переобъявление типа платформы - + Unknown element Неизвестный элемент - - - - + + + + Error Ошибка @@ -1140,50 +1322,50 @@ This is probably because the settings were changed between the Cppcheck versions Невозможно загрузить %1. Cppcheck установлен некорректно. Вы можете использовать --data-dir=<directory> в командной строке для указания расположения файлов конфигурации. Обратите внимание, что --data-dir предназначен для использования сценариями установки. При включении данной опции, графический интерфейс пользователя не запускается. - + Open the report file Открыть файл с отчетом - + Text files (*.txt) Текстовые файлы (*.txt) - + CSV files (*.csv) CSV файлы(*.csv) - + Project files (*.cppcheck);;All files(*.*) Файлы проекта (*.cppcheck);;Все файлы(*.*) - + Select Project File Выберите файл проекта - - - - + + + + Project: Проект: - + No suitable files found to analyze! Не найдено подходящих файлов для анализа - + C/C++ Source Исходный код C/C++ - + Compile database @@ -1196,22 +1378,22 @@ This is probably because the settings were changed between the Cppcheck versions Borland C++ Builder 6 - + Select files to analyze Выбор файлов для анализа - + Select directory to analyze Выбор каталога для анализа - + Select the configuration that will be analyzed Выбор используемой конфигурации - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1220,7 +1402,7 @@ Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1231,7 +1413,7 @@ Do you want to proceed? Вы хотите продолжить? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1240,104 +1422,104 @@ Do you want to stop the analysis and exit Cppcheck? Вы хотите остановить анализ и выйти из Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML файлы (*.xml);;Текстовые файлы (*.txt);;CSV файлы (*.csv) - + Build dir '%1' does not exist, create it? Директория для сборки '%1' не существует, создать? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1346,22 +1528,22 @@ Analysis is stopped. Невозможно импортировать '%1', анализ остановлен - + Project files (*.cppcheck) Файлы проекта (*.cppcheck) - + Select Project Filename Выберите имя файла для проекта - + No project file loaded Файл с проектом не загружен - + The project file %1 @@ -1377,12 +1559,12 @@ Do you want to remove the file from the recently used projects -list? Хотите удалить его из списка проектов? - + Install - + New version available: %1. %2 @@ -1439,26 +1621,31 @@ Options: NewSuppressionDialog + New suppression Новое подавление + Error ID ID + File name Имя файла + Line number Номер строки + Symbol name Имя символа @@ -1500,73 +1687,87 @@ Options: ProjectFile + Project File Файл проекта + Paths and Defines Каталоги и определения + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) Импорт проекта (Visual studio / compile database/ Borland C++ Builder 6) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' Defines должны быть разделены точкой с запятой ';' + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. Положите свои .cfg-файлы в один каталог с файлом проекта. Вы увидите их сверху. + Clang (experimental) + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + Max recursion in template instantiation + Filepaths in warnings will be relative to this path + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... @@ -1576,48 +1777,59 @@ Options: + MISRA rule texts Файл с текстами правил MISRA + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Скопируйте текст из Appendix A &quot;Summary of guidelines&quot; из фала правил MISRA C 2012 pdf в текстовый файл.</p></body></html> + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>Выберите:</p><p> * Анализ всех конфигураций Debug и Release</p><p> * Анализ только первой подходящей конфигурации Debug</p><p><br/></p></body></html> + + Browse... Обзор... + Analyze all Visual Studio configurations Анализировать все конфигурации Visual Studio + Selected VS Configurations + Paths: Пути: + + Add... Добавить... @@ -1625,6 +1837,9 @@ Options: + + + Edit Изменить @@ -1633,260 +1848,317 @@ Options: + + + + Remove Удалить + Undefines: Удаленные макроопределения: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 Удаленные макроопределения должны быть разделены точкой с запятой, например: UNDEF1;UNDEF2;UNDEF3 + Include Paths: Пути заголовочных файлов: + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Types and Functions + + Analysis Анализ + This is a workfolder that Cppcheck will use for various purposes. + Parser + Cppcheck (built in) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + Check that each class has a safe public interface + Limit analysis + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) Проверить код в неиспользуемых шаблонах + Max CTU depth Максимальная глубина CTU + Enable inline suppressions Включить inline-подавление ошибок + 2025 + Misra C++ + 2008 + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting + External tools Внешние инструменты + Up Вверх + Down Вниз + Platform Платформа + Warning options Опции предупреждений + Root path: Корневой каталог: + Warning tags (separated by semicolon) Теги предупреждений (через ';') + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Каталог сборки Cppcheck + Libraries Библиотеки + Suppressions Подавления + Add Добавить + + Addons Дополнения + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 + Thread safety + Coding standards Стандарты кодирования + Misra C + 2012 + + 2023 + Cert C++ + Bug hunting (Premium) + Defines: Объявленные макроопределения: @@ -2526,21 +2798,25 @@ To toggle what kind of errors are shown, open view menu. + Results Результаты + Critical errors + Analysis Log Лог анализа + Warning Details Детали предупреждения @@ -2549,26 +2825,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad Блокнот + Copy or write some C/C++ code here: Исходный код C/C++: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": При необходимости введите имя файла и нажмите "Проверить": + filename имя файла + Check Проверить @@ -2577,21 +2858,25 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences Параметры + General Общие + Add... Добавить... + Number of threads: Количество потоков исполнения: @@ -2601,57 +2886,68 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations Check all #ifdef configurations Проверять все варианты #ifdef конфигураций + Show full path of files Показывать полные пути к файлам + Show "No errors found" message when no errors found Показывать сообщение, если ошибок не найдено + Display error Id in column "Id" Отображать номер ошибки в колонке "id" + Enable inline suppressions Включить inline-подавление ошибок + Check for inconclusive errors also Показывать также спорные ошибки + Show statistics on check completion Показывать статистику после завершения проверки + Check for updates + Show internal warnings in log Показывать внутренние предупреждения в логе + Addons Дополнения + Python binary (leave this empty to use python in the PATH) Python (оставьте пустым для использования python из PATH) @@ -2659,117 +2955,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon Дополнение MISRA + MISRA rule texts file Файл с текстами правил MISRA: + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Скопируйте текст из Appendix A &quot;Summary of guidelines&quot; из фала правил MISRA C 2012 pdf в текстовый файл.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clang (оставьте пустым для использования clang из PATH) + Visual Studio headers Заголовочные файлы Visual Studio + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Путь до заголовочных файлов Visual Studio headers, разделенных символом ';'.</p><p>Вы можете открыть командную строку Visual Studio, ввести &quot;SET INCLUDE&quot; и скопировать пути.</p></body></html> + Code Editor Редактор + Code Editor Style Оформление + System Style + Default Light Style + Default Dark Style + Custom + Remove Удалить + Max count: + Applications Приложения + + Edit... Изменить... + Set as default Установить по умолчанию + Reports Отчёты + Save all errors when creating report Сохранять все ошибки при создании отчёта + Save full path to files in reports Сохранять полные пути к файлам в отчётах + Language Язык @@ -2826,6 +3148,8 @@ To toggle what kind of errors are shown, open view menu. + + Statistics @@ -2833,113 +3157,135 @@ To toggle what kind of errors are shown, open view menu. + Project Проект + Project: Проект: + Paths: Пути: + Include paths: Включенные пути: + Defines: Объявленные макроопределения: + Undefines: Удаленные макроопределения: + Previous Scan Последнее сканирование + Path Selected: Выбранный путь: + Number of Files Scanned: Количество просканированных файлов: + Scan Duration: Продолжительность сканирования: + Errors: Ошибки: + Warnings: Предупреждения: + Stylistic warnings: Стилистические предупреждения: + Portability warnings: Предупреждения переносимости: + Performance issues: Проблемы с производительностью: + Information messages: Информационные сообщения: + Active checkers: + Checkers + History История + File: Файл: + Copy to Clipboard Скопировать в буфер обмена + Pdf Export Экспорт PDF @@ -3089,6 +3435,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details diff --git a/gui/cppcheck_sr.ts b/gui/cppcheck_sr.ts index 9579d88b695..81da0d77edf 100644 --- a/gui/cppcheck_sr.ts +++ b/gui/cppcheck_sr.ts @@ -5,27 +5,32 @@ About + About Cppcheck About Cppcheck + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - A tool for static C/C++ code analysis. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 This program is licensed under the terms @@ -33,11 +38,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 Visit Cppcheck homepage at %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application Add a new application + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -67,21 +76,25 @@ Parameters: -l(line) (file) + &Name: + &Executable: + &Parameters: + Browse Browse @@ -126,16 +139,19 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index @@ -153,16 +169,19 @@ Parameters: -l(line) (file) LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -171,86 +190,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor + Open + Save + Save as + Functions + Sort + Add + Filter: + Comments + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit @@ -296,11 +332,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -311,11 +349,13 @@ Parameters: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -326,78 +366,99 @@ Parameters: -l(line) (file) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -410,785 +471,906 @@ Parameters: -l(line) (file) + A&nalyze + Standard Standard + &File &File + &View &View + &Toolbars + C++ standard + &C standard C standard + &Edit &Edit + &License... &License... + A&uthors... A&uthors... + &About... &About... + &Files... &Files... + + Analyze files Check files + Ctrl+F Ctrl+F + &Directory... &Directory... + + Analyze directory Check directory + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop &Stop + + Stop analysis Stop checking + Esc Esc + &Save results to file... &Save results to file... + Ctrl+S Ctrl+S + &Quit &Quit + &Clear results &Clear results + &Preferences &Preferences - + + + Show errors - + + + Show warnings + + Show performance warnings + Show &hidden - - + + Information + Show information messages + Show portability warnings + Show Cppcheck results + Clang + Show Clang results + &Filter + Filter results + Windows 32-bit ANSI Windows 32-bit ANSI + Windows 32-bit Unicode Windows 32-bit Unicode + Unix 32-bit Unix 32-bit + Unix 64-bit Unix 64-bit + Windows 64-bit Windows 64-bit + &Print... + Print the Current Report + Print Pre&view... + Open a Print Preview Dialog for the Current Results + Open library editor + &Check all &Check all + Checking for updates + Hide + + Report + Filter + &Reanalyze modified files &Recheck modified files + Reanal&yze all files + Ctrl+Q + Style war&nings + E&rrors + &Uncheck all &Uncheck all + Collapse &all Collapse &all + &Expand all &Expand all + &Standard + Standard items + Toolbar + &Categories + Error categories + &Open XML... + Open P&roject File... + Ctrl+Shift+O + Sh&ow Scratchpad... + &New Project File... + Ctrl+Shift+N + &Log View + Log View + C&lose Project File + &Edit Project File... + &Statistics + &Warnings + Per&formance warnings + &Information + &Portability + P&latforms + C++&11 + C&99 + &Posix + C&11 + &C89 + &C++03 + &Library Editor... + &Auto-detect language + &Enforce C++ + E&nforce C + C++14 C++14 + Reanalyze and check library + Check configuration (defines, includes) + C++17 C++17 + C++20 C++20 + Compliance report... + Normal + Autosar + EULA... + Thread Details + Show thread details + &Contents + Categories + + Show style warnings + Open the help contents + F1 F1 + &Help &Help - - + + Quick Filter: - + Select configuration - + Found project file: %1 Do you want to load this project file instead? - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License License - + Authors Authors - + Save the report file Save the report file - - + + XML files (*.xml) XML files (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + You must close the project file before selecting new files or directories! - + The library '%1' contains unknown elements: %2 - + Unsupported format - + Duplicate platform type - + Platform type redefined - + Unknown element - - - - + + + + Error - + Open the report file - + Text files (*.txt) Text files (*.txt) - + CSV files (*.csv) - + Project files (*.cppcheck);;All files(*.*) - + Select Project File - - - - + + + + Project: - + + None of the selected files is part of the project, nothing to analyze. + + + + No suitable files found to analyze! - + C/C++ Source - + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1196,76 +1378,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename - + No project file loaded - + The project file %1 @@ -1276,67 +1458,67 @@ Do you want to remove the file from the recently used projects -list? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1380,26 +1562,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1441,75 +1628,91 @@ Options: ProjectFile + Project File + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. + MISRA rule texts + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: + + Add... @@ -1517,6 +1720,9 @@ Options: + + + Edit @@ -1525,305 +1731,371 @@ Options: + + + + Remove + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: + Up + Down + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Platform + + Analysis + This is a workfolder that Cppcheck will use for various purposes. + Clang (experimental) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) + Max CTU depth + Max recursion in template instantiation + Warning options + Root path: + Filepaths in warnings will be relative to this path + Warning tags (separated by semicolon) + Enable inline suppressions + 2025 + Misra C++ + 2008 + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting + External tools + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + Types and Functions + Libraries + Parser + Cppcheck (built in) + Check that each class has a safe public interface + Limit analysis + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... + Suppressions + Add + + Addons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 + Thread safety + Coding standards + Misra C + 2012 + + 2023 + Cert C++ + Bug hunting (Premium) + Defines: @@ -2427,21 +2699,25 @@ To toggle what kind of errors are shown, open view menu. + Results Results + Critical errors + Analysis Log + Warning Details @@ -2450,26 +2726,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename + Check @@ -2478,76 +2759,91 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences Preferences + General General + Add... + Number of threads: Number of threads: + Force checking all #ifdef configurations Check all #ifdef configurations + Show full path of files Show full path of files + Show "No errors found" message when no errors found Show "No errors found" message when no errors found + Display error Id in column "Id" + Enable inline suppressions + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2555,117 +2851,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove + Max count: + Applications Applications + + Edit... + Set as default + Reports Reports + Save all errors when creating report Save all errors when creating report + Save full path to files in reports Save full path to files in reports + Language @@ -2718,6 +3040,8 @@ To toggle what kind of errors are shown, open view menu. + + Statistics @@ -2725,113 +3049,135 @@ To toggle what kind of errors are shown, open view menu. + Project + Project: + Paths: + Include paths: + Defines: + Undefines: + Previous Scan + Path Selected: + Number of Files Scanned: + Scan Duration: + Errors: + Warnings: + Stylistic warnings: + Portability warnings: + Performance issues: + Information messages: + Active checkers: + Checkers + History + File: + Copy to Clipboard + Pdf Export @@ -2981,6 +3327,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details diff --git a/gui/cppcheck_sv.ts b/gui/cppcheck_sv.ts index 79bc48d06fc..80726023e09 100644 --- a/gui/cppcheck_sv.ts +++ b/gui/cppcheck_sv.ts @@ -5,27 +5,32 @@ About + About Cppcheck Om Cppcheck + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Ett verktyg för statisk analys av C/C++ kod. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 Cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 This program is licensed under the terms @@ -33,11 +38,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 Hemsida: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application Lägg till program + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Parametrar: -l(line) (file) + &Name: Namn: + &Executable: Körbar fil: + &Parameters: Parametrar: + Browse Bläddra @@ -138,16 +151,19 @@ Parametrar: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index @@ -165,16 +181,19 @@ Parametrar: -l(line) (file) LibraryAddFunctionDialog + Add function Lägg till funktion + Function name(s) Funktion namn + Number of arguments Antal argument @@ -183,86 +202,103 @@ Parametrar: -l(line) (file) LibraryDialog + Library Editor Library Editor + Open Öppna + Save Spara + Save as Spara som + Functions Funktioner + Sort Sortera + Add Lägg till + Filter: Filter: + Comments Kommentar + noreturn noreturn + False False + True True + Unknown Vet ej + return value must be used retur värde måste användas + ignore function in leaks checking Ignorera funktionen när cppcheck letar efter läckor + Arguments Argument + Edit Redigera @@ -308,11 +344,13 @@ Parametrar: -l(line) (file) LibraryEditArgDialog + Edit argument Konfigurera argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -326,11 +364,13 @@ Exempel: + Not bool Ej bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -344,78 +384,99 @@ Exempel: + Not null Ej null + Not uninit Ej uninit + String Sträng + Format string Format sträng + Min size of buffer Minsta storlek för buffer + + Type Typ + + None Ingen + + argvalue argvalue + + mul mul + + strlen strlen + + Arg Arg + + Arg2 Arg2 + and och + Valid values Tillåtna värden @@ -428,579 +489,700 @@ Exempel: + A&nalyze Analysera + Standard Standard + &File &Arkiv + &View &Visa + &Toolbars Verktygsfält + C++ standard C++ standard + &C standard C standard C standard + &Edit &Redigera + &License... &Licens... + A&uthors... &Utvecklat av... + &About... &Om... + &Files... &Filer... + + Analyze files Check files Analysera filer + Ctrl+F Ctrl+F + &Directory... &Katalog... + + Analyze directory Check directory Analysera mapp + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop &Stoppa + + Stop analysis Stop checking Stoppa analys + Esc Esc + &Save results to file... &Spara resultat till fil... + Ctrl+S Ctrl+S + &Quit &Avsluta + &Clear results &Töm resultat + &Preferences &Inställningar - + + + Show errors Visa fel - + + + Show warnings Visa varningar + + Show performance warnings Visa prestanda varningar + Show &hidden Visa dolda - - + + Information Information + Show information messages Visa informations meddelanden + Show portability warnings Visa portabilitets varningar + Show Cppcheck results Visa Cppcheck resultat + Clang Clang + Show Clang results Visa Clang resultat + &Filter &Filter + Filter results Filtrera resultat + Windows 32-bit ANSI Windows 32-bit ANSI + Windows 32-bit Unicode Windows 32-bit Unicode + Unix 32-bit Unix 32-bit + Unix 64-bit Unix 64-bit + Windows 64-bit Windows 64-bit + &Print... Skriv ut... + Print the Current Report Skriv ut aktuell rapport + Print Pre&view... Förhandsgranska utskrift... + Open a Print Preview Dialog for the Current Results Öppnar förhandsgranskning för nuvarande resultat + Open library editor Öppna library editor + &Check all &Kryssa alla + Checking for updates + Hide Dölj + + Report + Filter Filter + &Reanalyze modified files &Recheck modified files Analysera om ändrade filer + Reanal&yze all files Analysera om alla filer + Ctrl+Q + Style war&nings Style varningar + E&rrors Fel + &Uncheck all Kryssa &ur alla + Collapse &all Ingen bra översättning! &Fäll ihop alla + &Expand all &Expandera alla + &Standard &Standard + Standard items Standard poster + Toolbar Verktygsfält + &Categories &Kategorier + Error categories Fel kategorier + &Open XML... &Öppna XML... + Open P&roject File... Öppna Projektfil... + Ctrl+Shift+O + Sh&ow Scratchpad... Visa Scratchpad... + &New Project File... Ny projektfil... + Ctrl+Shift+N + &Log View + Log View Logg vy + C&lose Project File Stäng projektfil + &Edit Project File... Redigera projektfil... + &Statistics Statistik + &Warnings Varningar + Per&formance warnings Optimerings varningar + &Information Information + &Portability Portabilitet + P&latforms Plattformar + C++&11 C++11 + C&99 C99 + &Posix Posix + C&11 C11 + &C89 C89 + &C++03 C++03 + &Library Editor... Library Editor... + &Auto-detect language Detektera språk automatiskt + &Enforce C++ Tvinga C++ + E&nforce C Tvinga C + C++14 C++14 + Reanalyze and check library + Check configuration (defines, includes) + C++17 C++17 + C++20 C++20 + Compliance report... + Normal + Autosar + EULA... + Thread Details + Show thread details + &Contents &Innehåll + Categories Kategorier + + Show style warnings Visa stil varningar + Open the help contents Öppna hjälp + F1 F1 + &Help &Hjälp - - + + Quick Filter: Snabbfilter: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration Välj konfiguration - + Found project file: %1 Do you want to load this project file instead? @@ -1009,97 +1191,97 @@ Do you want to load this project file instead? Vill du ladda denna projektfil istället? - + File not found Filen hittades ej - + Bad XML Ogiltig XML - + Missing attribute Attribut finns ej - + Bad attribute value Ogiltigt attribut värde - + Unsupported format Format stöds ej - + Duplicate define - + Failed to load the selected library '%1'. %2 Misslyckades att ladda valda library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Licens - + Authors Utvecklare - + Save the report file Spara rapport - - + + XML files (*.xml) XML filer (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1108,37 +1290,37 @@ This is probably because the settings were changed between the Cppcheck versions En trolig orsak är att inställningarna ändrats för olika Cppcheck versioner. Kontrollera programinställningarna. - + You must close the project file before selecting new files or directories! Du måste stänga projektfilen innan nya filer eller sökvägar kan väljas! - + The library '%1' contains unknown elements: %2 Library filen '%1' har element som ej hanteras: %2 - + Duplicate platform type Dubbel plattformstyp - + Platform type redefined Plattformstyp definieras igen - + Unknown element Element hanteras ej - - - - + + + + Error Fel @@ -1147,50 +1329,50 @@ En trolig orsak är att inställningarna ändrats för olika Cppcheck versioner. Misslyckades att ladda %1. Din Cppcheck installation är ej komplett. Du kan använda --data-dir<directory> på kommandoraden för att specificera var denna fil finns. Det är meningen att --data-dir kommandot skall köras under installationen,så GUIt kommer ej visas när --data-dir används allt som händer är att en inställning görs. - + Open the report file Öppna rapportfilen - + Text files (*.txt) Text filer (*.txt) - + CSV files (*.csv) CSV filer (*.csv) - + Project files (*.cppcheck);;All files(*.*) Projektfiler (*.cppcheck);;Alla filer(*.*) - + Select Project File Välj projektfil - - - - + + + + Project: Projekt: - + No suitable files found to analyze! Inga filer hittades att analysera! - + C/C++ Source - + Compile database @@ -1199,22 +1381,22 @@ En trolig orsak är att inställningarna ändrats för olika Cppcheck versioner. Visual Studio - + Select files to analyze Välj filer att analysera - + Select directory to analyze Välj mapp att analysera - + Select the configuration that will be analyzed Välj konfiguration som kommer analyseras - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1223,7 +1405,7 @@ Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1231,7 +1413,7 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1240,104 +1422,104 @@ Do you want to stop the analysis and exit Cppcheck? Vill du stoppa analysen och avsluta Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML filer (*.xml);;Text filer (*.txt);;CSV filer (*.csv) - + Build dir '%1' does not exist, create it? Build dir '%1' existerar ej, skapa den? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1346,22 +1528,22 @@ Analysis is stopped. Misslyckades att importera '%1', analysen stoppas - + Project files (*.cppcheck) Projekt filer (*.cppcheck) - + Select Project Filename Välj Projektfil - + No project file loaded Inget projekt laddat - + The project file %1 @@ -1378,12 +1560,12 @@ Do you want to remove the file from the recently used projects -list? Vill du ta bort filen från 'senast använda projekt'-listan? - + Install - + New version available: %1. %2 @@ -1440,26 +1622,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1501,65 +1688,79 @@ Options: ProjectFile + Project File Projektfil + Paths and Defines Sökvägar och defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) Importera Projekt (Visual Studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' Defines separeras med semicolon ';' + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. Obs: Lägg dina egna .cfg filer i samma folder som projekt filen. De skall isåfall visas ovan. + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>Du har ett val:</p><p> * Analysera alla Debug och Release konfigurationer</p><p> * Analysera bara den första matchande Debug konfigurationen</p><p><br/></p></body></html> + + Browse... + Analyze all Visual Studio configurations Analysera alla Visual Studio konfigurationer + Selected VS Configurations + Paths: Sökvägar: + + Add... Lägg till... @@ -1567,6 +1768,9 @@ Options: + + + Edit Redigera @@ -1575,310 +1779,377 @@ Options: + + + + Remove Ta bort + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: Include sökvägar: + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + This is a workfolder that Cppcheck will use for various purposes. + Clang (experimental) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + Max recursion in template instantiation + Filepaths in warnings will be relative to this path + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files + Exclude folder... + Exclude file... + Enable inline suppressions Använd inline suppressions + Misra C + 2012 + + 2023 + 2025 + MISRA rule texts + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Misra C++ + 2008 + Cert C++ + Safety profiles (defined in C++ core guidelines) + Bug hunting (Premium) + External tools + Up Upp + Down Ned + Platform + + Analysis + Parser + Cppcheck (built in) + Check that each class has a safe public interface + Limit analysis + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) + Max CTU depth + Warning options + Root path: Bas sökväg: + Warning tags (separated by semicolon) Varnings taggar (separerade med semikolon) + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck build dir (whole program analys, incremental analys, statistik, etc) + Types and Functions + Libraries Libraries + Suppressions Suppressions + Add Lägg till + + Addons Addons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 Y2038 + Thread safety Tråd säkerhet + Coding standards Kodstandarder + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Bug hunting @@ -1892,6 +2163,7 @@ Options: + Defines: Defines: @@ -2530,21 +2802,25 @@ För att ställa in vilka fel som skall visas använd visa menyn. + Results Resultat + Critical errors + Analysis Log Analys Log + Warning Details Varningsdetaljer @@ -2553,26 +2829,31 @@ För att ställa in vilka fel som skall visas använd visa menyn. ScratchPad + Scratchpad Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename Filnamn + Check Analysera @@ -2581,21 +2862,25 @@ För att ställa in vilka fel som skall visas använd visa menyn. Settings + Preferences Inställningar + General Allmänt + Add... Lägg till... + Number of threads: Antal trådar: @@ -2605,57 +2890,68 @@ För att ställa in vilka fel som skall visas använd visa menyn. + Force checking all #ifdef configurations Check all #ifdef configurations Kontrollera alla #ifdef konfigurationer + Show full path of files Visa den fulla sökvägen för filer + Show "No errors found" message when no errors found Visa "Inga fel hittades" meddelande när inga fel hittas + Display error Id in column "Id" Visa meddelande id i kolumn "Id" + Enable inline suppressions Använd inline suppressions + Check for inconclusive errors also Kör inconclusive analys + Show statistics on check completion Visa statistik när analys är klar + Check for updates + Show internal warnings in log Visa interna fel i loggen + Addons Addons + Python binary (leave this empty to use python in the PATH) Python binär fil (lämna tom för att använda python i PATH) @@ -2663,117 +2959,143 @@ För att ställa in vilka fel som skall visas använd visa menyn. + + + ... ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clang sökväg (lämna tom för att använda PATH) + Visual Studio headers Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Sökvägar till Visual Studio headers, separerade med semikolon ';'.</p><p>Du kan öppna en Visual Studio command prompt, och skriva &quot;SET INCLUDE&quot;. Sedan kopiera och klistra in sökvägarna.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove Ta bort + Max count: + Applications Program + + Edit... Redigera... + Set as default Sätt förvald + Reports Rapporter + Save all errors when creating report Spara alla fel + Save full path to files in reports Spara fulla sökvägar + Language Språk @@ -2830,6 +3152,8 @@ För att ställa in vilka fel som skall visas använd visa menyn. + + Statistics @@ -2837,113 +3161,135 @@ För att ställa in vilka fel som skall visas använd visa menyn. + Project Projekt + Project: Projekt: + Paths: Sökvägar: + Include paths: Include sökvägar: + Defines: Defines: + Undefines: + Previous Scan Föregående analys + Path Selected: Vald sökväg: + Number of Files Scanned: Antal analyserade filer: + Scan Duration: Analys tid: + Errors: Fel: + Warnings: Varningar: + Stylistic warnings: Stil varningar: + Portability warnings: Portabilitets varningar: + Performance issues: Prestanda varningar: + Information messages: Informations meddelanden: + Active checkers: + Checkers + History Historik + File: Fil: + Copy to Clipboard Kopiera + Pdf Export Pdf Export @@ -3093,6 +3439,7 @@ För att ställa in vilka fel som skall visas använd visa menyn. ThreadDetails + Thread Details diff --git a/gui/cppcheck_zh_CN.ts b/gui/cppcheck_zh_CN.ts index 17a85231255..3bc2a493818 100644 --- a/gui/cppcheck_zh_CN.ts +++ b/gui/cppcheck_zh_CN.ts @@ -5,38 +5,45 @@ About + About Cppcheck 关于 Cppcheck + Version %1 版本 %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - C/C++ 静态代码分析工具。 + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. 版权所有 © 2007-%1 Cppcheck 团队。 + This program is licensed under the terms of the GNU General Public License version 3 该程序在 GNU 通用公共授权版本 3 的条款下发布 + Visit Cppcheck homepage at %1 访问 Cppcheck 主页: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -46,11 +53,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application 添加应用程序 + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -76,21 +85,25 @@ Parameters: -l(line) (file) + &Name: 名称(&N): + &Executable: 可执行文件(&E): + &Parameters: 参数(&P): + Browse 浏览 @@ -135,16 +148,19 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck GUI 帮助 + Contents 内容 + Index 索引 @@ -162,16 +178,19 @@ Parameters: -l(line) (file) LibraryAddFunctionDialog + Add function 添加函数 + Function name(s) 函数名 + Number of arguments 参数个数 @@ -180,86 +199,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor 库编辑器 + Open 打开 + Save 保存 + Save as 另存为 + Functions 函数 + Sort 排序 + Add 添加 + Filter: 过滤: + Comments 注释 + noreturn 无返回 + False 否 + True 是 + Unknown 未知 + return value must be used 返回值必须被使用 + ignore function in leaks checking 在泄漏检查中忽略函数 + Arguments 参数 + Edit 编辑 @@ -305,11 +341,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument 编辑参数 + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -325,11 +363,13 @@ Parameters: -l(line) (file) + Not bool 非布尔值 + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -345,78 +385,99 @@ Parameters: -l(line) (file) + Not null 非空 + Not uninit 非未初始化 + String 字符串 + Format string 格式化字符串 + Min size of buffer 最小缓冲区大小 + + Type 类型 + + None 无 + + argvalue argvalue + + mul mul + + strlen strlen + + Arg 参数1 + + Arg2 参数2 + and 并且 + Valid values 有效值 @@ -429,567 +490,683 @@ Parameters: -l(line) (file) + &File 文件(&F) + &View 查看(&V) + &Toolbars 工具栏(&T) + &Help 帮助(&H) + C++ standard C++ 标准 + &C standard C standard &C 标准 + &Edit 编辑(&E) + Standard 标准 + Categories 分类 + &License... 许可证(&L)... + A&uthors... 作者(&U)... + &About... 关于(&A)... + &Files... 文件(&F)... + + Analyze files Check files 分析文件 + Ctrl+F Ctrl+F + &Directory... 目录(&D)... + + Analyze directory Check directory 分析目录 + Ctrl+D Ctrl+D + Ctrl+R Ctrl+R + &Stop 停止(&S) + + Stop analysis Stop checking 停止分析 + Esc Esc + &Save results to file... 保存结果到文件(&S)... + Ctrl+S Ctrl+S + &Quit 退出(&Q) + &Clear results 清空结果(&C) + &Preferences 首选项(&P) + + Show style warnings 显示风格警告 - + + + Show errors 显示错误 - - + + Information 信息 + Show information messages 显示信息消息 + Show portability warnings 显示可移植性警告 + Show Cppcheck results 显示 Cppcheck 结果 + Clang Clang + Show Clang results 显示 Clang 结果 + &Filter 滤器(&F) + Filter results 过滤结果 + Windows 32-bit ANSI + Windows 32-bit Unicode + Unix 32-bit + Unix 64-bit + Windows 64-bit + &Print... 打印(&P)... + Print the Current Report 打印当前报告 + Print Pre&view... 打印预览(&v)... + Open a Print Preview Dialog for the Current Results 打开当前结果的打印预览窗口 + Open library editor 打开库编辑器 + C&lose Project File 关闭项目文件(&L) + &Edit Project File... 编辑项目文件(&E)... + &Statistics 统计(&S) - + + + Show warnings 显示警告 + + Show performance warnings 显示性能警告 + Show &hidden 显示隐藏项(&H) + &Check all 全部选中(&C) + Checking for updates + Hide 隐藏 + + Report + A&nalyze 分析(&A) + Filter 滤器 + &Reanalyze modified files &Recheck modified files 重新分析已修改的文件(&R) + Reanal&yze all files 重新分析全部文件(&y) + Ctrl+Q Ctrl+Q + Style war&nings 风格警告(&n) + E&rrors 编辑(&r) + &Uncheck all 全部取消选中(&U) + Collapse &all 全部折叠(&A) + &Expand all 全部展开(&E) + &Standard 标准(&S) + Standard items 标准项 + &Contents 内容(&C) + Open the help contents 打开帮助内容 + F1 F1 + Toolbar 工具栏 + &Categories 分类(&C) + Error categories 错误分类 + &Open XML... 打开 XML (&O)... + Open P&roject File... 打开项目文件(&R)... + Ctrl+Shift+O Ctrl+Shift+O + Sh&ow Scratchpad... 显示便条(&o)... + &New Project File... 新建项目文件(&N)... + Ctrl+Shift+N Ctrl+Shift+N + &Log View 日志视图(&L) + Log View 日志视图 + &Warnings 警告(&W) + Per&formance warnings 性能警告(&f) + &Information 信息(&I) + &Portability 可移植性(&P) + P&latforms 平台(&l) + C++&11 C++&11 + C&99 C&99 + &Posix &Posix + C&11 C&11 + &C89 &C89 + &C++03 &C++03 + &Library Editor... 库编辑器(&L)... + &Auto-detect language 自动检测语言(&A) + &Enforce C++ &Enforce C++ + E&nforce C E&nforce C + C++14 C++14 + Reanalyze and check library 重新分析并检查库 + Check configuration (defines, includes) 检查配置(defines, includes) + C++17 C++17 + C++20 C++20 + Compliance report... + Normal 常规 + Autosar + EULA... + Thread Details + Show thread details - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -998,23 +1175,28 @@ This is probably because the settings were changed between the Cppcheck versions 这可能是因为 Cppcheck 不同版本间的设置有所不同。请检查(并修复)编辑器应用程序设置,否则编辑器程序可能不会正确启动。 - + You must close the project file before selecting new files or directories! 在选择新的文件或目录之前,你必须先关闭此项目文件! - - + + Quick Filter: 快速滤器: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration 选择配置 - + Found project file: %1 Do you want to load this project file instead? @@ -1023,64 +1205,64 @@ Do you want to load this project file instead? 你是否想加载该项目文件? - + The library '%1' contains unknown elements: %2 库 '%1' 包含未知元素: %2 - + File not found 文件未找到 - + Bad XML 无效的 XML - + Missing attribute 缺失属性 - + Bad attribute value 无效的属性值 - + Unsupported format 不支持的格式 - + Duplicate platform type 重复的平台类型 - + Platform type redefined 平台类型重定义 - + Unknown element 位置元素 - + Failed to load the selected library '%1'. %2 选择的库 '%1' 加载失败。 %2 - - - - + + + + Error 错误 @@ -1089,108 +1271,108 @@ Do you want to load this project file instead? 加载 %1 失败。您的 Cppcheck 安装已损坏。您可以在命令行添加 --data-dir=<目录> 参数来指定文件位置。请注意,'--data-dir' 参数应当由安装脚本使用,因此,当使用此参数时,GUI不会启动,所发生的一切只是配置了设置。 - - + + XML files (*.xml) XML 文件(*.xml) - + Open the report file 打开报告文件 - + License 许可证 - + Authors 作者 - + Save the report file 保存报告文件 - + Text files (*.txt) 文本文件(*.txt) - + CSV files (*.csv) CSV 文件(*.csv) - + Project files (*.cppcheck);;All files(*.*) 项目文件(*.cppcheck);;所有文件(*.*) - + Select Project File 选择项目文件 - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Install - + New version available: %1. %2 - - - - + + + + Project: 项目: - + No suitable files found to analyze! 没有找到合适的文件来分析! - + C/C++ Source C/C++ 源码 - + Compile database Compile database @@ -1203,22 +1385,22 @@ Analysis is stopped. Borland C++ Builder 6 - + Select files to analyze 选择要分析的文件 - + Select directory to analyze 选择要分析的目录 - + Select the configuration that will be analyzed 选择要分析的配置 - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1227,44 +1409,44 @@ Do you want to proceed analysis without using any of these project files? - + Duplicate define - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1275,7 +1457,7 @@ Do you want to proceed? 你想继续吗? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1284,77 +1466,77 @@ Do you want to stop the analysis and exit Cppcheck? 您想停止分析并退出 Cppcheck 吗? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML 文件 (*.xml);;文本文件 (*.txt);;CSV 文件 (*.csv) - + Build dir '%1' does not exist, create it? 构建文件夹 '%1' 不能存在,创建它吗? - + To check the project using addons, you need a build directory. 要使用插件检查项目,您需要一个构建目录。 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1363,22 +1545,22 @@ Do you want to stop the analysis and exit Cppcheck? 导入 '%1' 失败,分析已停止 - + Project files (*.cppcheck) 项目文件 (*.cppcheck) - + Select Project Filename 选择项目文件名 - + No project file loaded 项目文件未加载 - + The project file %1 @@ -1446,26 +1628,31 @@ Options: NewSuppressionDialog + New suppression 新建抑制 + Error ID 错误 ID + File name 文件名 + Line number 行号 + Symbol name 符号名 @@ -1487,48 +1674,57 @@ Options: ProjectFile + Project File 项目文件 + Paths and Defines 路径和定义 + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) 导入项目 (Visual studio / compile database/ Borland C++ Builder 6) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' 定义必须用分号分隔。例如:DEF1;DEF2=5;DEF3=int + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. 注意:把你自己的 .cfg 文件放在和项目文件相同的文件夹中。你应该在上面看到它们。 + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. 如果添加了标记,您将能够右键单击警告并设置其中一个标记。您可以手动对警告进行分类。 + Exclude source files 排除源文件 + Exclude folder... 排除文件夹... + Exclude file... 排除文件... @@ -1538,48 +1734,59 @@ Options: + MISRA rule texts MISRA 规则文本 + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>从 MISRA C 2012 PDF 的附录 A &quot;指南摘要&quot; 复制/粘贴文本到一个文本文件。</p></body></html> + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>您有一个选择:</p><p> * 分析所有的 Debug 和 Release 配置</p><p> * 只分析第一个匹配的 Debug 配置</p><p><br/></p></body></html> + + Browse... 浏览... + Analyze all Visual Studio configurations 分析全部 Visual Studio 配置 + Selected VS Configurations 已选择的 VS 配置 + Paths: 路径: + + Add... 添加... @@ -1587,6 +1794,9 @@ Options: + + + Edit 编辑 @@ -1595,280 +1805,341 @@ Options: + + + + Remove 移除 + Undefines: 未定义: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 未定义必须用分号分隔。例如:UNDEF1;UNDEF2;UNDEF3 + Include Paths: 包含目录: + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Types and Functions 类型和函数 + + Analysis 分析 + This is a workfolder that Cppcheck will use for various purposes. 这是一个 Cppcheck 将用于各种目的的工作文件夹。 + Parser 解析器 + Cppcheck (built in) Cppcheck (内建) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + Check that each class has a safe public interface 检查每个类是否有一个安全的公共接口 + Limit analysis 极限分析 + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) 检查未使用模板中的代码(正常情况下应该是打开,但理论上可以忽略未使用模板中的警告) + Max CTU depth 最大 CTU 深度 + Enable inline suppressions 启用内联方案 + 2025 + Misra C++ + 2008 + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting + External tools 外部工具 + Up 向上 + Down 向下 + Platform 平台 + Clang (experimental) Clang (实验性的) + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. 如果你想要设计你的类尽可能的灵活和健壮,那么公共接口必须非常健壮。Cppcheck 将假设参数可以取 *任何* 值。 + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) 检查头文件中的代码(通常应该是打开的。如果您想要一个有限的快速分析,那么关掉它)) + Max recursion in template instantiation 模板实例化中的最大递归 + Warning options 警告选项 + Root path: 根路径: + Filepaths in warnings will be relative to this path 警告中的文件路径将相对于此路径 + Warning tags (separated by semicolon) 警告标志(用分号隔开) + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck 构建目录 (整个程序分析、增量分析、统计数据等) + Libraries 库 + Suppressions 抑制 + Add 添加 + + Addons 插件 + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. 注意:插件需要安装 <a href="https://www.python.org/">Python</a>。 + Y2038 Y2038 + Thread safety 线程安全 + Coding standards 编码标准 + Misra C + 2012 + + 2023 + Cert C++ + Bug hunting (Premium) @@ -1882,6 +2153,7 @@ Options: + Defines: 定义: @@ -2424,21 +2696,25 @@ Please check the application path and parameters are correct. ResultsView + Results 结果 + Critical errors + Analysis Log 分析日志 + Warning Details 警告详情 @@ -2549,26 +2825,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad 便条 + Copy or write some C/C++ code here: 在这里复制或输入一些 C/C++ 代码: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": 可选择输入文件名 (主要用来自动语言检测) 然后点击 "检查": + filename 文件名 + Check 检查 @@ -2577,21 +2858,25 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences 首选项 + General 常规 + Add... 添加... + Number of threads: 线程个数: @@ -2601,57 +2886,68 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations Check all #ifdef configurations 强制检查所有 #ifdef 配置 + Show full path of files 显示文件的完整路径 + Show "No errors found" message when no errors found 当未找到错误,显示“未发现错误”消息 + Display error Id in column "Id" 在列“Id”中显示错误 Id + Enable inline suppressions 启用内联方案 + Check for inconclusive errors also 检查不确定的错误 + Show statistics on check completion 检查完成后显示统计数据 + Check for updates + Show internal warnings in log 在日志中显示内建警告 + Addons 插件 + Python binary (leave this empty to use python in the PATH) Python 二进制 (留空将使用 PATH 路径中的 python) @@ -2659,117 +2955,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon MISRA 插件 + MISRA rule texts file MISRA 规则文本文件 + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>从 MISRA C 2012 PDF 的附录 A &quot;指南摘要&quot; 复制/粘贴文本到一个文本文件。</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clang 路径 (留空将使用系统 PATH 路径) + Visual Studio headers Visual Studio 头文件 + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Visual Studio 头文件路径,用分号 ';' 分割。</p><p>你可以打开一个 Visual Studio 命令提示符,输入 &quot;SET INCLUDE&quot;。然后复制/粘贴路径。</p></body></html> + Code Editor 代码编辑器 + Code Editor Style 代码编辑器风格 + System Style 系统风格 + Default Light Style 默认浅色风格 + Default Dark Style 默认深色风格 + Custom 自定义 + Remove 移除 + Max count: + Applications 应用程序 + + Edit... 编辑... + Set as default 设为默认 + Reports 报告 + Save all errors when creating report 创建报告时,保存所有错误 + Save full path to files in reports 在报告中保存文件的完整路径 + Language 语言 @@ -2826,6 +3148,8 @@ To toggle what kind of errors are shown, open view menu. + + Statistics @@ -2833,113 +3157,135 @@ To toggle what kind of errors are shown, open view menu. + Project 项目 + Project: 项目: + Paths: 路径: + Include paths: 包含路径: + Defines: 定义: + Undefines: 未定义: + Previous Scan 上一次扫描 + Path Selected: 选中的路径: + Number of Files Scanned: 扫描的文件数: + Scan Duration: 扫描时间: + Errors: 错误: + Warnings: 警告: + Stylistic warnings: Stylistic 警告: + Portability warnings: 可移植性警告: + Performance issues: 性能警告: + Information messages: 信息: + Active checkers: + Checkers + History 历史 + File: 文件: + Copy to Clipboard 复制到剪贴板 + Pdf Export 导出 PDF @@ -3089,6 +3435,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details diff --git a/gui/cppcheck_zh_TW.ts b/gui/cppcheck_zh_TW.ts index 6be1247835e..b1951e9f29f 100644 --- a/gui/cppcheck_zh_TW.ts +++ b/gui/cppcheck_zh_TW.ts @@ -5,26 +5,31 @@ About + About Cppcheck 關於 Cppcheck + Version %1 版本 %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - 一款靜態 C/C++ 程式碼分析工具。 + Copyright © 2007-%1 Cppcheck team. 著作權 © 2007-%1 Cppcheck 團隊。 + This program is licensed under the terms of the GNU General Public License version 3 該程式是根據 GNU 通用公眾授權條款第 3 版 @@ -32,11 +37,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 訪問 Cppcheck 主頁: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -46,11 +53,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application 新增應用程式 + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -66,21 +75,25 @@ Parameters: -l(line) (file) + &Name: 名稱(&N): + &Executable: 執行檔(&E): + &Parameters: 參數(&P): + Browse 瀏覽 @@ -140,16 +153,19 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck GUI 幫助 + Contents 內容 + Index 索引 @@ -167,16 +183,19 @@ Parameters: -l(line) (file) LibraryAddFunctionDialog + Add function 新增函式 + Function name(s) 函式名稱 + Number of arguments 引數數量 @@ -185,86 +204,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor 程式庫編輯器 + Open 開啟 + Save 儲存 + Save as 另存為 + Functions 函式 + Sort 排序 + Add 新增 + Filter: + Comments 註釋 + noreturn + False + True + Unknown 未知 + return value must be used + ignore function in leaks checking + Arguments 引數 + Edit 編輯 @@ -308,11 +344,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument 編輯引數 + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -323,11 +361,13 @@ Parameters: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -338,78 +378,99 @@ Parameters: -l(line) (file) + Not null + Not uninit + String 字串 + Format string + Min size of buffer + + Type 型別 + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values 有效值 @@ -422,532 +483,644 @@ Parameters: -l(line) (file) + Checking for updates 檢查更新 + Hide 隱藏 + &File 檔案(&F) + &View 檢視(&V) + &Toolbars 工具條(&T) + + Report + &Help 幫助(&H) + A&nalyze 分析(&N) + C++ standard C++ 標準 + &C standard C 標準(&C) + &Edit 編輯(&E) + Standard 標準 + Categories 分類 + Filter 篩選 + &License... 授權(&L)... + A&uthors... 作者(&U)... + &About... 關於(&A)... + &Files... 檔案(&F)... + + Analyze files 分析檔案 + Ctrl+F Ctrl+F + &Directory... 目錄(&D)... + + Analyze directory 分析目錄 + Ctrl+D Ctrl+D + &Reanalyze modified files 重新分析已修改的檔案(&R) + Ctrl+R Ctrl+R + Reanal&yze all files 重新分析所有檔案(&Y) + &Stop 停止(&S) + + Stop analysis 停止分析 + Esc Esc + &Save results to file... 儲存結果為檔案(&S)... + Ctrl+S Ctrl+S + &Quit 退出(&Q) + Ctrl+Q Ctrl+Q + &Clear results 清除結果(&C) + &Preferences 偏好設定(&P) + Style war&nings 樣式警告(&N) + + Show style warnings 顯示樣式警告 + E&rrors 錯誤(&R) - + + + Show errors 顯示錯誤 + &Check all 全部檢查(&C) + &Uncheck all + Collapse &all 全部摺疊(&A) + &Expand all 全部展開(&E) + &Standard 標準(&S) + Standard items 標準項目 + &Contents 內容(&C) + Open the help contents 開啟幫助內容 + F1 F1 + Toolbar 工具條 + &Categories 分類(&C) + Error categories 錯誤分類 + &Open XML... 開啟 XML(&O)... + Open P&roject File... 開啟專案檔(&R)... + Ctrl+Shift+O Ctrl+Shift+O + Sh&ow Scratchpad... + &New Project File... 新增專案檔(&N)... + Ctrl+Shift+N Ctrl+Shift+N + &Log View 日誌檢視(&L) + Log View 日誌檢視 + C&lose Project File 關閉專案檔(&L) + &Edit Project File... 編輯專案檔(&E)... + &Statistics 統計資料(&S) + &Warnings 警告(&W) - + + + Show warnings 顯示警告 + Per&formance warnings 效能警告(&F) + + Show performance warnings 顯示下效能警告 + Show &hidden 顯示隱藏項目(&H) + &Information 資訊(&I) + Show information messages 顯示資訊訊息 + &Portability 可移植性(&P) + Show portability warnings 顯示可移植性警告 + Show Cppcheck results 顯示 Cppcheck 結果 + Clang Clang + Show Clang results 顯示 Clang 結果 + &Filter 篩選(&F) + Filter results 篩選結果 + Windows 32-bit ANSI Windows 32 位元 ANSI + Windows 32-bit Unicode Windows 32 位元 Unicode + Unix 32-bit Unix 32 位元 + Unix 64-bit Unix 64 位元 + Windows 64-bit Windows 64 位元 + P&latforms 平臺(&L) + C++&11 C++&11 + C&99 C&99 + &Posix + C&11 C&11 + &C89 &C89 + &C++03 &C++03 + &Print... 列印(&P)... + Print the Current Report 列印當前報告 + Print Pre&view... 列印預覽(&V)... + Open a Print Preview Dialog for the Current Results 開啟當前結果的列印預覽視窗 + &Library Editor... 程式庫編輯器(&L)... + Open library editor 開啟程式庫編輯器 + &Auto-detect language 自動偵測語言(&A) + &Enforce C++ + E&nforce C + C++14 C++14 + Reanalyze and check library 重新分析並檢查程式庫 + Check configuration (defines, includes) 檢查組態 (定義、包含) + C++17 C++17 + C++20 C++20 + Compliance report... + Normal @@ -961,21 +1134,25 @@ Parameters: -l(line) (file) + Autosar + EULA... + Thread Details + Show thread details @@ -1003,43 +1180,48 @@ Options: Cppcheck GUI - 命令行參數 - - + + Quick Filter: 快速篩選: - - - - + + + + Project: 專案: - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + + None of the selected files is part of the project, nothing to analyze. + + + + No suitable files found to analyze! 找不到適合的檔案來分析! - + You must close the project file before selecting new files or directories! 您必須在選取新檔案或目錄之前關閉該專案檔! - + C/C++ Source C/C++ 來源檔 - + Compile database 編譯資料庫 @@ -1052,145 +1234,145 @@ This is probably because the settings were changed between the Cppcheck versions Borland C++ Builder 6 - + Select files to analyze 選取要分析的檔案 - + Select directory to analyze 選取要分析的目錄 - + Select configuration 選取組態 - + Select the configuration that will be analyzed 選取要分析的組態 - + Found project file: %1 Do you want to load this project file instead? - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - - + + Information 資訊 - + The library '%1' contains unknown elements: %2 - + File not found 找不到檔案 - + Bad XML - + Missing attribute - + Bad attribute value - + Unsupported format 未支援的格式 - + Duplicate platform type 重複的平臺型別 - + Platform type redefined 平臺型別重定義 - + Duplicate define - + Unknown element 未知的元素 - + Failed to load the selected library '%1'. %2 無法載入選取的程式庫 '%1'。 %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - - - - + + + + Error 錯誤 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1198,18 +1380,18 @@ Do you want to proceed? - - + + XML files (*.xml) XML 檔案 (*.xml) - + Open the report file 開啟報告檔 - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1218,77 +1400,77 @@ Do you want to stop the analysis and exit Cppcheck? 您想停止分析並離開 Cppcheck 嗎? - + About 關於 - + License 授權 - + Authors 作者 - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML 檔案 (*.xml);;文字檔 (*.txt);;CSV 檔案 (*.csv) - + Save the report file 儲存報告檔 - + Text files (*.txt) 文字檔 (*.txt) - + CSV files (*.csv) CSV 檔案 (*.csv) - + Project files (*.cppcheck);;All files(*.*) 專案檔 (*.cppcheck);;所有檔案 (*.*) - + Select Project File 選取專案檔 - + Build dir '%1' does not exist, create it? 建置目錄 '%1' 不存在,是否建立它? - + To check the project using addons, you need a build directory. - + Failed to open file 無法開啟檔案 - + Unknown project file format 未知的專案檔格式 - + Failed to import project file 無法匯入專案檔 - + Failed to import '%1': %2 Analysis is stopped. @@ -1297,62 +1479,62 @@ Analysis is stopped. 停止分析。 - + Failed to import '%1' (%2), analysis is stopped - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1361,22 +1543,22 @@ Analysis is stopped. 無法匯入 '%1',停止分析 - + Project files (*.cppcheck) 專案檔 (*.cppcheck) - + Select Project Filename 選取專案檔案名稱 - + No project file loaded - + The project file %1 @@ -1393,12 +1575,12 @@ Do you want to remove the file from the recently used projects -list? 您要從最近使用的專案列表中移除該檔案嗎? - + Install 安章 - + New version available: %1. %2 可用的新版本: %1. %2 @@ -1407,26 +1589,31 @@ Do you want to remove the file from the recently used projects -list? NewSuppressionDialog + New suppression 新建抑制 + Error ID 錯誤 ID + File name 檔案名稱 + Line number 行號 + Symbol name 符號名稱 @@ -1468,48 +1655,59 @@ Do you want to remove the file from the recently used projects -list? ProjectFile + Project File 專案檔 + Paths and Defines 路徑與定義 + Import Project (Visual studio / compile database/ Borland C++ Builder 6) 匯入專案 (Visual Studio / 編譯資料庫 / Borland C++ Builder 6) + + Browse... 瀏覽... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + Analyze all Visual Studio configurations 分析所有 Visual Studio 組態 + Selected VS Configurations 選取 VS 組態 + Paths: 路徑: + + Add... 新增... @@ -1517,6 +1715,9 @@ Do you want to remove the file from the recently used projects -list? + + + Edit 編輯 @@ -1525,274 +1726,334 @@ Do you want to remove the file from the recently used projects -list? + + + + Remove 移除 + Defines: 定義: + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int + Undefines: 未定義: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: 包含路徑: + Up + Down + Include file + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + Browse.. + Types and Functions 型別與函式 + Platform 平臺 + Libraries 程式庫 + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. + + Analysis 分析 + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + This is a workfolder that Cppcheck will use for various purposes. + Parser 剖析器 + Cppcheck (built in) Cppcheck (內建) + Clang (experimental) + Check level + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check that each class has a safe public interface + Limit analysis + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) + Max CTU depth + Max recursion in template instantiation + Warning options 警告選項 + Root path: 根路徑: + Filepaths in warnings will be relative to this path + Warning tags (separated by semicolon) 警告標記 (由分號分隔) + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + Exclude source files 排除來源檔 + Exclude folder... 排除資料夾... + Exclude file... 排除檔案... + Suppressions 抑制 + Add 新增 + Enable inline suppressions + + Addons + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. + Y2038 Y2038 + Thread safety 執行緒安全 + Coding standards + Misra C + 2012 + + 2023 + 2025 + Misra C++ + 2008 @@ -1802,16 +2063,19 @@ Do you want to remove the file from the recently used projects -list? + MISRA rule texts + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + ... ... @@ -1821,41 +2085,49 @@ Do you want to remove the file from the recently used projects -list? + Cert C + CERT-INT35-C: int precision (if size equals precision, you can leave empty) + Cert C++ + Autosar + Safety profiles (defined in C++ core guidelines) + Bug hunting (Premium) + Bug hunting + External tools 外部工具 @@ -2382,21 +2654,25 @@ Please check the application path and parameters are correct. ResultsView + Results 結果 + Critical errors + Analysis Log 分析日誌 + Warning Details 警告詳細資訊 @@ -2502,26 +2778,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename 檔案名稱 + Check @@ -2530,122 +2811,147 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences 偏好設定 + General 一般 + Number of threads: 執行緒數量: + Max count: + Force checking all #ifdef configurations + Show full path of files 顯示檔案的完整路徑 + Show "No errors found" message when no errors found + Display error Id in column "Id" + Enable inline suppressions + Check for inconclusive errors also + Show statistics on check completion + Check for updates 檢查更新 + Show internal warnings in log 顯示日誌中的內部警告 + Applications 應用程式 + Add... 新增... + + Edit... 編輯... + Remove 移除 + Set as default 設定為預設值 + Reports 報告 + Save all errors when creating report + Save full path to files in reports 在報告中儲存檔案的完整路徑 + Language 語言 + Addons + Python binary (leave this empty to use python in the PATH) @@ -2653,71 +2959,87 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon + MISRA rule texts file MISRA 規則文字檔 + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) + Visual Studio headers Visual Studio 標頭檔 + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor 程式碼編輯器 + Code Editor Style 程式碼編輯器樣式 + System Style 系統樣式 + Default Light Style + Default Dark Style + Custom 自訂 @@ -2770,6 +3092,8 @@ To toggle what kind of errors are shown, open view menu. + + Statistics @@ -2777,113 +3101,135 @@ To toggle what kind of errors are shown, open view menu. + Project 專案 + Project: 專案: + Paths: 路徑: + Include paths: 包含路徑: + Defines: 定義: + Undefines: 未定義: + Previous Scan 上一次掃描 + Path Selected: 選取的路徑: + Number of Files Scanned: 已掃描的檔案數量: + Scan Duration: 掃描時間: + Errors: 錯誤: + Warnings: 警告: + Stylistic warnings: + Portability warnings: 可移植性警告: + Performance issues: 效能議題: + Information messages: 資訊訊息: + Active checkers: + Checkers + History 歷史紀錄 + File: 檔案: + Copy to Clipboard 複製到剪貼簿 + Pdf Export Pdf 匯出 @@ -3033,6 +3379,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details diff --git a/gui/manualtest/docs/handbook.html b/gui/manualtest/docs/handbook.html new file mode 100644 index 00000000000..2bc1f6beba4 --- /dev/null +++ b/gui/manualtest/docs/handbook.html @@ -0,0 +1,351 @@ + + + + + +gui-test Handbook + + + +
+ +
+
gui-test
+

gui-test Handbook

+

gui-test turns the manual test descriptions for cppcheck-gui into automated tests: Claude explores the GUI once and writes a script, and from then on the script is replayed without any AI.

+

Updated 2026-10-22

+ +

Overview

+

A test is written in plain words: steps and an expected result. gen.py starts the application, lets Claude do the test by hand through guitest.py, and saves what it did as a script of do and expect steps. run.py replays the scripts, one fresh application per test, and writes an HTML report. web.py is the web interface around all of it.

+ +
+ + + Claude writes the script once, every run replays it without AI + + + + + + + + + + A test in words + steps, expected result + + gen.py + Claude does it once + + Replay passes? + + Script saved + in the test file + + run.py + replays it, no AI + + report.html + and results.json + yes + no: Claude gets the reason, up to 3 tries + every run + +
From a test in words to a report: gen.py once, run.py every run.
+
+ +

The application runs in its own Xvfb display with its own D-Bus and accessibility bus, so your desktop is never touched. Elements are found through AT-SPI by their accessible name and role, not by screen positions.

+
+ + + + + + + + + + + +
PartWhat it does
web.pyWeb interface: edit test files and test projects, generate scripts, run tests, open reports
gen.pyClaude drives the GUI once per test and writes its script; the script is saved only if a replay passes
run.pyReplays the scripts without AI and writes the report and results.json
runner.pyExecutes one script step by step, used by both gen.py and run.py
guitest.pyCommand line tool that starts the session and clicks, types and reads the GUI
testfile.pyThe test file format, test matrices, test projects and outdated-script detection
report.pyWrites the HTML report
tests/The test files (*.json) and test projects (tests/projects/NAME)
results/One directory per run with the report, logs and screenshots
+ +

Getting started

+

Start web.py with the cppcheck-gui binaries you want to test, then open http://127.0.0.1:8080/ in a browser.

+
./web.py --opensource ~/cppcheck/build/bin/cppcheck-gui \
+         --premium ~/premium/build/bin/cppcheck-gui --src ~/cppcheck
+
    +
  • --opensource and --premium each take the binary of that edition. Give one, both or none; without a binary you can edit tests but not generate or run them.
  • +
  • --src is the git checkout the binaries are built from. It is only used for the git SHA, branch and commit in the report; the default is the directory of the binary.
  • +
  • --dir (default tests), --out (default results) and --port (default 8080) change where test files and results live and which port is used. The server only listens on 127.0.0.1.
  • +
+

The tools the session needs must be installed: Xvfb, xdotool, dbus-daemon, the AT-SPI bus launcher and Python with the GObject bindings and Pillow. tesseract is only needed for ocr checks, and the claude command line tool only for generating scripts. On Ubuntu (checked on 26.04) they come from these packages:

+
sudo apt install xvfb xdotool dbus at-spi2-core python3-gi gir1.2-atspi-2.0 python3-pil tesseract-ocr tesseract-ocr-eng
+
+ + + + + + + + + +
PackageNeeded for
xvfbThe virtual display the application runs in
xdotoolKeyboard input and mouse clicks
dbusThe session's own D-Bus (dbus-daemon)
at-spi2-coreThe accessibility bus (/usr/libexec/at-spi-bus-launcher)
python3-gi, gir1.2-atspi-2.0Python access to AT-SPI, to find and read elements
python3-pilScreenshots, and cropping them for the report
tesseract-ocr, tesseract-ocr-engOnly for ocr checks
+ +

The web interface

+
    +
  • Left column: the test files and the test projects. The ▶ button next to a file runs all tests in it.
  • +
  • Header: Save, Run all (every test in every file), the edition picker (opensource, premium or both) and a status message.
  • +
  • Main area: the open test file, the open test or test project. Each has an ✕ in the top right corner to close it.
  • +
  • Job box: while a script is generated or tests run, it shows the progress, the results as they come and a link to the report. Its ✕ hides it until the next job.
  • +
+ +

Writing tests

+

A test is written as if for a person: steps in plain words and the result to expect. Claude reads exactly these words when it makes the script, so be concrete.

+

Tests live in test files (tests/*.json), one file per area of the GUI. Click a file in the left column to see its title and its tests, New file to create one, or Import .md to convert an older markdown test file (sections ## Test: NAME). Click a test in the list to edit it, + New test to add one. Nothing is written to disk until you press Save; a dot after the file name means there are unsaved changes.

+
+ + + + + + + + + + + +
FieldWhat to write
NameUnique within the file. Shown in reports and the history, so a rename starts a new history line
TicketOptional, e.g. a Trac ticket number
enabledUntick to leave the test out of every run without deleting it
EditionsThe editions the test runs in; default both. The script is the same for all, {edition} is replaced by the edition's name
StepsOne step per line, e.g. "Open File > New project file". Use {axis} for matrix values
Expected resultWhat must be true at the end. Every part becomes a check in the script
TODOWhy a test can't be automated yet, e.g. "cannot be automated". Leave empty otherwise
Test projectsThe projects whose files the test uses, see below
Test matrixAxes with comma-separated values; every combination is a separate run
+ +

Test matrix

+

An axis named platform with the values native, unix32 and an axis lang with C, C++ give 4 runs. A step like "Select platform {platform}" gets the value of each run. The runs are named after the test plus the values, e.g. Relative paths [platform=unix32, lang=C], and each one gets its own line in the report. The editor shows how many runs a matrix gives.

+ +

Test projects

+

A test project is a directory of files that tests use: source files, a .cppcheck project file, a .cfg file. They live in tests/projects/NAME and are edited under Test projects in the left column. Before every run the project is copied to a new empty directory, so a test always starts with the same files, even if an earlier test changed them. In a script the copy is {project:NAME}, or {project} when the test has only one project.

+

Changing a file in a project makes the scripts of the tests that use it outdated, because the script was made for the old files.

+ +

Scripts

+

A test runs only when it has an up-to-date script. Generate it with Claude once, check it, and from then on it is replayed without AI.

+ +

Generating a script

+

Open the test and press Generate script. gen.py starts the application with the test projects in place, and Claude does the test by hand: it looks at the accessibility tree, clicks and types, and checks the expected result. It then answers with a script. The script is replayed from scratch and saved in the test file only if that replay passes. When the replay fails, Claude is told why and tries again, 3 attempts in all. While a script is generated the editor is locked.

+
    +
  • A matrix test is explored with the first combination. The script uses {axis} where the values belong.
  • +
  • When the application does not do what the expected result says, Claude still writes the script that checks it and marks that one check "observed_fail": true. The script is saved, and the test fails until the bug is fixed. That is a finding, not a broken script.
  • +
  • When the test can't be done through the GUI, Claude answers CANNOT: <reason> and no script is saved.
  • +
+

Read the generated script before you trust it, especially its expect steps: they decide what the test proves.

+ +

The script format

+

A script is a JSON list of steps. Each step has do (an action) or expect (a check), and a label in the words of the test that is shown when it fails. Elements are found by name (part of the accessible name, case-insensitive) and role, and if needed path, nth and exact.

+
[
+  {"do": "click", "name": "Help", "role": "menu item", "label": "Open the Help menu"},
+  {"do": "click", "name": "Contents", "role": "menu item", "label": "Choose Help > Contents"},
+  {"expect": "visible", "name": "Cppcheck GUI help", "role": "dialog", "label": "A help window opens"}
+]
+
+ + + + + + + + + + + + + + + +
StepWhat it does
do: click, rightclick, doubleclick, focus, hoverAct on an element; "mouse": true clicks with the mouse
do: settextReplace the text of an edit field with value
do: type, keyKeyboard input to the focused element; keys uses xdotool names like ctrl+o, Return
do: waitWait for an element to appear, or with "gone": true to disappear
do: restartQuit and start the application again; settings are kept
do: sleep, write, copyPause, write a file in {work}, or copy a file in a test project
expect: visible, goneAn element is shown, or is not
expect: stateAn element is or is not checked, enabled, focused, selected, expanded...
expect: valueThe value or text of a field equals something
expect: text, ocrText is (not) on the screen; ocr reads pixels with tesseract, for context menus AT-SPI can't see
expect: fileA file contains, does not contain or matches a regex
expect: alive, exitedThe application is still running, or has quit by itself with status 0
expect: captureSearch regex in an element and save group 1 as a name; {name} in the later steps is replaced by it, for text that changes such as the version
+

Checks and actions wait for their element up to timeout seconds, 5 by default. The placeholders {work}, {home}, {project:NAME}, {axis} and {edition} are replaced before the run, captured values when the capture step has passed. The full description is under How to write a script below the script in the editor.

+ +

Outdated scripts

+

A script is made for the test's steps, expected result, TODO, matrix and the files of its test projects. When any of those change, the script is OUTDATED: it is kept but skipped by every run, and the editor says so in red. You then have three choices:

+
    +
  1. Generate script again.
  2. +
  3. Approve the script when the change does not affect it, e.g. a typo fix in a step.
  4. +
  5. Edit the script by hand in the editor; it has to be valid JSON before you can save.
  6. +
+ +

Running tests

+

Every test starts a fresh application with an empty HOME and fresh copies of its test projects, so tests never depend on each other.

+ +

From the web interface

+

Pick the edition in the header first: opensource, premium or both. Then:

+
    +
  • Run test in an open test runs that test, all its matrix combinations.
  • +
  • ▶ next to a test file runs every test in that file.
  • +
  • Run all runs every test in every file.
  • +
+

With "both", the run is done for one edition and then the other, and each edition gets its own report. The job box shows the test and step that is running and the results as they come. Stop ends the run at once: the test that was running is SKIP "aborted by user", and the report is written with the tests that are done. Only one job runs at a time.

+ +

From the command line

+
./run.py ~/cppcheck/build/bin/cppcheck-gui tests/application.json tests/projects.json --edition opensource
+
+ + + + + + + + + +
OptionMeaning
APPThe binary to test
FILE ...One or more test files
--edition opensource|premiumAPP is this edition; only the tests for it run. Without it all tests run and there is no history per edition
--test TEXTOnly tests whose name contains TEXT (case-insensitive)
--id IDOnly the test with this id, all its matrix combinations
--src DIRGit checkout for the SHA in the report; default the directory of APP
--out DIRWhere the run directories go; default results
+

run.py prints one line per test and the path of the report, and exits with 1 when a test failed, so it can be used in a script or CI job. Scripts are generated the same way with ./gen.py APP FILE.json; it takes --edition, --test, --id, --force (also tests that have a script), --model and --attempts.

+ +

What happens in a run

+
    +
  1. The tests to run are selected: enabled, for this edition, not filtered out. Tests without a script, or with an outdated one, are SKIP.
  2. +
  3. For each test, a new Xvfb session starts, the test projects are copied, and the application is started.
  4. +
  5. The script is replayed step by step. After every step that acts on the GUI (click, type, key, settext, restart...) a screenshot is saved.
  6. +
  7. At the end, or at the first failing step, the step log, a final screenshot and the application log are saved, and the log is searched for a crash.
  8. +
  9. After the last test, results.json and report.html are written in the run directory.
  10. +
+ +

Reading the report

+

Each run writes one self-contained report.html; read it top-down: the verdict first, then what changed, then the details of each failure.

+
+ The top of a report: verdict, run details, number boxes, results per test file, the last runs and the status history +
The top of a report from three runs of two test files.
+
+
+ + + + + + + + + + +
PartWhat it tells you
HeaderFAILED or PASSED with the count, and the run details: date, edition, duration, git SHA ("+ uncommitted changes" when the checkout was dirty), branch, commit, application, host, test files
Number boxesPassed, failed (how many crashed the app), skipped, duration, and with history the change against the previous run
Results per test fileA bar per file, green/red/amber. Shows whether failures are in one part of the GUI. Only when the run has more than one file
The last runsPass, fail and skip counts for up to 15 runs of the same edition; the number on a column is its failures
Changed since the previous runNEW failures, tests that are fixed, and tests that are STILL FAILING
Status historyA row per test that did not pass in every run: "new failure", "failing since 10/22 12:00 (b8b27a)", "fixed since", "flaky: 4 changes", "never ran"
Failed and skipped testsA card per test: the reason, the step strip, the screenshots after the last 3 steps and the final screen with a red frame
All testsEvery test grouped by file, with its time, links to its files and its step screenshots
+ +

The step strip

+

Every test has a strip with a cell per script step: green passed, red failed, grey not run. A failure at step 2 of 14 usually means the test or the application broke early. A failure at the last step usually means one check is wrong, or the bug the check describes. Under the strip it says e.g. "failed at step 9 of 14", "not run" or "did not start".

+ +

Files in a run directory

+

A run directory is results/<date>-<time>-<edition>-<sha>/, e.g. 20261022-120317-opensource-b8b27a95/. The test name becomes the file name, with everything but letters, digits and _ turned into _.

+
+ + + + + + + + +
FileContent
report.htmlThe report
results.jsonThe run details and every result. The history is read from these files
NAME.txtThe step log: each step, ok or FAIL, its time and its screenshot
NAME.pngThe screen at the end of the test, or when it failed
NAME.stepNN.pngThe screen after step NN, for steps that act on the GUI
NAME.app.logWhat the application printed
+

The history only uses runs of the same edition in the same --out directory that have a results.json. Runs made before 22 Oct 2026 don't have one and are left out. Tests are matched by name.

+ +

Troubleshooting

+

Start with the reason in the report and the final screenshot: together they usually say whether the application, the script or the test is wrong.

+
+ + + + + + + + + + + + +
What you seeWhat it means and what to do
SKIP "no script"The test has no script yet. Generate one, or write it by hand
SKIP "OUTDATED SCRIPT"The test or one of its test projects changed after the script was made. Generate again, or approve the script if the change doesn't matter to it
FAIL "timeout waiting for element to appear"An element with that name and role wasn't found within the timeout. The reason lists the most similar visible elements and the open windows: a renamed button shows up there. Fix the name in the script, or regenerate
FAIL with observed_fail=True in the reasonClaude saw the application not do what the expected result says when it made the script. It is a known bug, and the test fails until the bug is fixed
FAIL with CRASHThe application was not running at the end of the test, or its log has a fatal message (SIGSEGV, SIGABRT, an assertion, AddressSanitizer, QFATAL...). A crash fails the test even if every step passed. Read NAME.app.log. A test that quits the application with status 0 is not a crash
FAIL "could not start"The session or the application did not start. Check that the packages under Getting started are installed, and that the binary runs
"flaky" in the status historyThe test changes between PASS and FAIL. Usually timing: give the slow step a longer timeout, or add a wait step before it
A context menu can't be foundSome menus are not in the accessibility tree. Check them with an ocr step and choose with key Down and Return
"path ... is outside the test directories"write, copy and file steps may only use {work}, {home} and the test project copies
Generate script or Run test is greyed outweb.py has no binary for the edition picked in the header, or the test is not for that edition. Run test also needs an up-to-date script
+ +

Reference

+

The commands, with all their options. web.py, run.py, gen.py and guitest.py also print their help with --help.

+
web.py [--opensource APP] [--premium APP] [--src DIR] [--dir tests] [--out results] [--port 8080]
+run.py APP FILE.json... [--edition opensource|premium] [--test TEXT] [--id ID] [--src DIR] [--out results]
+gen.py APP FILE.json [--edition opensource|premium] [--test TEXT] [--id ID] [--force] [--model M] [--attempts 3]
+testfile.py convert IN.md OUT.json
+ +

guitest.py

+

guitest.py is what Claude uses to drive the GUI, and you can use it by hand to look at the GUI the way a script sees it. The session lives in $GUITEST_STATE (default /tmp/guitest-<uid>/state.json).

+
guitest.py start ~/cppcheck/build/bin/cppcheck-gui
+guitest.py tree --showing --depth 3
+guitest.py click --name Help --role "menu item"
+guitest.py screenshot /tmp/screen.png
+guitest.py stop
+
+ + + + + + + + + + + + +
CommandWhat it does
start APP [ARGS]Start Xvfb, D-Bus, AT-SPI and the application. --home DIR (default an empty directory), --size 1280x900
stop, restartStop everything; quit and start the application again with its settings kept
alive, logExit 0 if the application runs; print what it wrote to stdout and stderr
windows, textsThe top level windows and dialogs; every visible text
treeThe accessibility tree. --showing only visible elements, --depth N, --path P one subtree
click, rightclick, doubleclick, focus, hoverAct on an element found with --name, --role, --path, --nth, --exact. --mouse for a real mouse click
settext VALUE, type VALUE, key KEYSSet a field's text; type into the focused element; press keys (xdotool names)
waitWait for an element, --timeout 10, --gone to wait until it disappears
info, suggestRole, name, text, value and states of one element; the visible elements most like --name/--role
ocr, screenshot FILEText on the screen read by tesseract; a PNG of the screen
+
+
+ + diff --git a/gui/manualtest/docs/report.png b/gui/manualtest/docs/report.png new file mode 100644 index 0000000000000000000000000000000000000000..2a24756da06c1b9514640eb22bb401d5a3737a33 GIT binary patch literal 149022 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 RD13Pgs_+6*CigPnKLE7l_v`=w literal 0 HcmV?d00001 diff --git a/gui/manualtest/gen.py b/gui/manualtest/gen.py new file mode 100755 index 00000000000..28a52264fe4 --- /dev/null +++ b/gui/manualtest/gen.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +"""Let Claude write the scripts of the tests. The scripts are replayed by run.py without AI. + + gen.py APP FILE.json [--edition opensource|premium] [--test SUBSTRING] [--force] [--model M] + +For every test without a script (or every test with --force) Claude gets the steps, drives the +GUI once with the guitest.py commands and answers with a script, see testfile.SCRIPT_DOC. +The script is run again from scratch and is saved in FILE.json only if that replay passes. +Matrix tests are explored with the first combination, the script uses {axis} where the +values of the matrix belong. With --edition APP is that edition, tests that are not for it are +left out; the script is used for all the editions of the test. +""" + +import argparse +import json +import os +import re +import signal +import subprocess +import sys +import tempfile + +import runner +import testfile + +HERE = os.path.dirname(os.path.abspath(__file__)) +GUITEST = os.path.join(HERE, 'guitest.py') + +PROMPT = """You are writing an automated GUI test script for a Qt application that has been started for you. +First do the test by hand, driving the application only with this command line tool (each call is separate, +the session is already running): + + python3 {guitest} + +{help} + +Tips: +- Start with `tree --showing --depth 3`, `windows` and `texts` to see what is on screen. +- Use `info --name X` to see the roles and states of an element, e.g. the [checked] state. +- Menu items: click the menu (e.g. --name File --role "menu item"), then click the entry. +- Keyboard input goes to the focused element: `focus` first, then `type` / `key`. +- The files of the test are in the test projects, they are already in place, use them as they are:{projects} + Do not make copies of them. In the script write {{project:NAME}} instead of the directory, it is another + directory every run. Anything else that you need can be created in {work} (use Write), do not touch other places. +- A menu that is on screen but not in `tree`/`texts` (e.g. a context menu) can be read with `ocr`; check it in the + script with {{"expect": "ocr", "contains": "..."}} and use the keyboard (`key Down`, `key Return`) to choose in it. +- Never read or modify the application source code. + +Test name: {name} + +{body} + +When you have done the test and checked the EXPECTED result, answer with a script that does the +same without you. The script is replayed by a program, so: +- use the exact roles and names that you saw. Give a name and a role. Use "path" only when nothing else tells + the element apart, paths change when the dialogs change (a file dialog's name field is found by its label) +- a name matches any element that contains it, so always give the role too: a toolbar button + "Preferences" and the dialog "Preferences" have the same name, the dialog is role "dialog" +- give every step a "label" that says what it is for, in the words of the test +- end with "expect" steps that check the EXPECTED result, one for each thing that has to be true +- the files of the test are the test projects, refer to them with {{project:NAME}} paths; files that + you made yourself are created by "write" steps (in {{work}}), not by you +- the application is started fresh before the script, with an empty HOME +- {matrixnote} +- {editionnote} + +{scriptdoc} + +{previous}Check the EXPECTED result honestly. If the application does not do what EXPECTED says that is a finding, not +a mistake in the script: still write the script that checks EXPECTED (it will fail, that is the point) and put +the line `OBSERVED: FAIL - ` before the JSON block and add "observed_fail": true to +the one check that fails. Otherwise put `OBSERVED: PASS`. + +Finish with the script as one JSON list in a ```json code block and nothing after it. +If the test cannot be done through the GUI answer with the single line CANNOT: . +""" + + +PREVIOUS = """An earlier attempt gave this script: +{script} + +When it was replayed from scratch by the program it failed: +{error} + +Find out what is wrong (look at the GUI again if needed) and answer with a corrected script. + +""" + + +def start_session(app, root, projects): + work = tempfile.mkdtemp(prefix='guitest-work-') + state = os.path.join(work, 'state.json') + env = dict(os.environ, GUITEST_STATE=state) + sub = lambda *a, **kw: subprocess.run([sys.executable, GUITEST, *a], env=env, text=True, **kw) + variables = testfile.materialize(root, projects, work) + r = sub('start', app, capture_output=True) + if r.returncode: + sys.exit('error: could not start: ' + (r.stdout + r.stderr).strip()) + with open(state) as f: + home = json.load(f)['env']['HOME'] + return work, home, env, sub, variables + + +def explore(app, root, test, combo, edition, model, previous=''): + """Claude does the test by hand and returns (script, error); with a script the error is (OBSERVED, reason).""" + work, home, env, sub, variables = start_session(app, root, test.get('projects', [])) + try: + note = ('this is a matrix test, you explore it with %s. Write {%s} in the script wherever that value ' + 'is used, the script is run once for every combination' % ( + ', '.join('%s=%s' % kv for kv in combo.items()), '}, {'.join(combo)) + ) if combo else 'there is no matrix, write the values in the script as they are' + others = [e for e in testfile.editions(test) if e != edition] + if not edition: + editionnote = 'there is one edition of the application' + elif others: + editionnote = ('the application is the %s edition. The same script is also run with the %s edition, ' + 'write {edition} where the name of the edition is shown' % (edition, ' and '.join(others))) + else: + editionnote = 'the script is only run with the %s edition' % edition + projects = ''.join('\n {project:%s} = %s, files: %s' % (n, variables['project:' + n], + ', '.join(sorted(testfile.project_files(root, n)))) + for n in test.get('projects', [])) or ' none' + prompt = PROMPT.format(guitest=GUITEST, help=sub('--help', capture_output=True).stdout, work=work, + name=test['name'], body=testfile.body(test, combo, edition), matrixnote=note, + editionnote=editionnote, + scriptdoc=testfile.SCRIPT_DOC, previous=previous, projects=projects) + cmd = ['claude', '-p', '--allowedTools', 'Bash(python3 %s:*)' % GUITEST, 'Read', 'Write', '--add-dir', work] + if model: + cmd += ['--model', model] + p = subprocess.run(cmd, input=prompt, env=env, text=True, capture_output=True, cwd=work) + finally: + sub('stop', capture_output=True) + m = re.findall(r'^CANNOT:\s*(.*)$', p.stdout, re.M) + if m: + return None, 'Claude: cannot be tested through the GUI: ' + m[-1] + m = re.findall(r'```json\s*(.*?)```', p.stdout, re.S) + if not m: + return None, 'no script in the answer of Claude: ' + (p.stdout.strip() or p.stderr.strip())[:300] + try: + script = json.loads(m[-1]) + except ValueError as e: + return None, 'the script is not valid JSON: %s' % e + errors = testfile.validate_script(script) + observed = re.findall(r'^OBSERVED:\s*(PASS|FAIL)\s*-?\s*(.*)$', p.stdout, re.M) + return (None, '; '.join(errors)) if errors else (script, observed[-1] if observed else ('PASS', '')) + + +def replay(app, root, test, script): + work, home, env, sub, variables = start_session(app, root, test.get('projects', [])) + try: + ok, msg, log = runner.run_script(sub, script, work, home, variables) + finally: + sub('stop', capture_output=True) + return ok, msg + + +def store(path, test_id, script, made_for): + """Put the script in the file as it is now, the file may have been saved by the web interface meanwhile.""" + doc = testfile.ensure_ids(testfile.load(path)) + for t in doc['tests']: + if t['id'] == test_id: + t['script'], t['script_for'] = script, made_for + testfile.save(path, doc) + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument('app') + ap.add_argument('file') + ap.add_argument('--edition', choices=testfile.EDITIONS, help='the edition that APP is, only the tests for it') + ap.add_argument('--test', help='only tests whose name contains this') + ap.add_argument('--id', help='only the test with this id') + ap.add_argument('--force', action='store_true', help='also tests that have a script') + ap.add_argument('--model') + ap.add_argument('--attempts', type=int, default=3, help='tries per test, a failed replay is told to Claude') + args = ap.parse_args() + signal.signal(signal.SIGINT, signal.default_int_handler) # also when started in the background + if not args.file.endswith('.json'): + sys.exit('error: FILE must be a .json test file, convert markdown with testfile.py convert') + app = os.path.abspath(args.app) + doc = testfile.ensure_ids(testfile.load(args.file)) + root = os.path.dirname(os.path.abspath(args.file)) + errors = testfile.validate(doc, root) + if errors: + sys.exit('error: ' + '; '.join(errors)) + + failed = 0 + for t in doc['tests']: + if args.test and args.test.lower() not in t['name'].lower() or args.id and t['id'] != args.id: + continue + if (t.get('script') and not args.force) or not t.get('enabled', True): + continue + if args.edition and args.edition not in testfile.editions(t): + print('%s: not a test for the %s edition' % (t['name'], args.edition), flush=True) + continue + combo = testfile.combinations(t)[0] + values = dict(combo, edition=args.edition) if args.edition else combo + made_for = testfile.definition_hash(t, root) # the test may be edited while Claude works + print('%s: exploring...' % t['name'], flush=True) + previous, saved = '', False + for attempt in range(1, args.attempts + 1): + script, err = explore(app, root, t, combo, args.edition, args.model, previous) + if script is not None: + # the replay uses the first combination, as the exploration did + observed, why = err + ok, err = replay(app, root, t, testfile.subst_deep(script, values)) + # a test that fails on the check that Claude saw fail by hand is a finding: the script is right + m = re.match(r'step (\d+)/', err) + finding = (not ok and observed == 'FAIL' and m and script[int(m.group(1)) - 1].get('observed_fail')) + if ok or finding: + store(args.file, t['id'], script, made_for) + print('%s: saved a script with %d steps (attempt %d)%s' % ( + t['name'], len(script), attempt, + '. It FAILS on a check, as Claude saw: ' + why if finding else ''), flush=True) + saved = True + break + err = 'the replay of the script failed:\n' + err + previous = PREVIOUS.format(script=json.dumps(script, indent=1), error=err) + print('%s: attempt %d failed. %s' % (t['name'], attempt, err), flush=True) + if re.search(r'usage limit|rate limit|limit reached|out of credits', str(err), re.I): + sys.exit('error: Claude refuses to answer (limit), stopping. Run gen.py again later.') + if err.startswith('Claude: cannot'): + break + if not saved: + failed += 1 + print('%s: no script saved' % t['name'], flush=True) + sys.exit(1 if failed else 0) + + +if __name__ == '__main__': + main() diff --git a/gui/manualtest/guitest.py b/gui/manualtest/guitest.py new file mode 100755 index 00000000000..881d9f27bc5 --- /dev/null +++ b/gui/manualtest/guitest.py @@ -0,0 +1,708 @@ +#!/usr/bin/env python3 +"""GUI test helper for Qt applications, driven through AT-SPI. + +The application runs in its own Xvfb display with its own D-Bus and +accessibility bus, so the real desktop is never touched. Each subcommand is a +separate process; the session state (environment, pids) lives in a JSON file. + + guitest.py start APP [ARGS...] start the session and the application + guitest.py tree show the accessibility tree + guitest.py click --name Recheck click an element + guitest.py stop kill everything + +State file: $GUITEST_STATE, default /tmp/guitest-/state.json +""" + +import argparse +import contextlib +import io +import glob +import json +import os +import re +import shutil +import signal +import subprocess +import struct +import sys +import tempfile +import threading +import time +import warnings + +warnings.filterwarnings("ignore") + +STATE = os.environ.get('GUITEST_STATE', '/tmp/guitest-%d/state.json' % os.getuid()) + + +# ---------------------------------------------------------------- session + +def load_state(): + try: + with open(STATE) as f: + return json.load(f) + except FileNotFoundError: + sys.exit('error: no session, run "guitest.py start APP" first') + + +# the shell tells how the application ended in app.log: 139 is SIGSEGV, 134 SIGABRT, 143 killed by us +WRAPPER = '"$@" & echo $! > "$0"; wait $!; echo "application exited with status $?" >&2' + + +def launch(cmd, env, tmp, log): + """Start the application. Returns the shell that waits for it and the pid of the application.""" + pidfile = os.path.join(tmp, 'app.pid') + if os.path.exists(pidfile): + os.remove(pidfile) + p = subprocess.Popen(['sh', '-c', WRAPPER, pidfile] + cmd, env=env, start_new_session=True, + stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT) + for _ in range(100): + try: + with open(pidfile) as f: + return p, int(f.read()) + except (OSError, ValueError): + time.sleep(0.05) + sys.exit('error: the application did not start') + + +def exit_info(state): + """' (exited with status 139, SIGSEGV)' when the log says how the application ended, else ''.""" + try: + with open(os.path.join(state['tmp'], 'app.log'), errors='replace') as f: + m = re.findall(r'application exited with status (\d+)', f.read()) + except OSError: + return '' + if not m: + return '' + code = int(m[-1]) + sig = ', ' + signal.Signals(code - 128).name if 128 < code < 160 else '' + return ' (exited with status %d%s)' % (code, sig) + + +def start(args): + if os.path.exists(STATE): + sys.exit('error: session already running (%s), run stop first' % STATE) + app = os.path.abspath(args.app) + if not os.access(app, os.X_OK): + sys.exit('error: not executable: ' + app) + for tool in ('Xvfb', 'xdotool', 'dbus-daemon', '/usr/libexec/at-spi-bus-launcher'): + if not shutil.which(tool) and not os.path.exists(tool): + sys.exit('error: missing ' + tool) + + tmp = tempfile.mkdtemp(prefix='guitest-') + runtime = os.path.join(tmp, 'run') + home = args.home or os.path.join(tmp, 'home') + os.makedirs(runtime, mode=0o700) + os.makedirs(home, exist_ok=True) + pids = [] + + def spawn(cmd, env, **kw): + kw.setdefault('stdout', subprocess.DEVNULL) + kw.setdefault('stderr', subprocess.DEVNULL) + p = subprocess.Popen(cmd, env=env, start_new_session=True, stdin=subprocess.DEVNULL, **kw) + pids.append(p.pid) + return p + + env = dict(os.environ) + env.update(XDG_RUNTIME_DIR=runtime, HOME=home) + for k in ('XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME', 'XDG_CONFIG_DIRS'): + env.pop(k, None) # QSettings must not find the real preferences + env.pop('WAYLAND_DISPLAY', None) + env.pop('DBUS_SESSION_BUS_ADDRESS', None) + + # session bus + out = subprocess.run(['dbus-daemon', '--session', '--fork', '--print-address', '--print-pid'], + env=env, capture_output=True, text=True, check=True).stdout.split() + env['DBUS_SESSION_BUS_ADDRESS'] = out[0] + pids.append(int(out[1])) + + # X server + r, w = os.pipe() + spawn(['Xvfb', '-displayfd', str(w), '-screen', '0', args.size + 'x24', '-fbdir', tmp], + env, pass_fds=(w,)) + os.close(w) + env['DISPLAY'] = ':' + os.read(r, 16).decode().strip() + os.close(r) + + # accessibility bus + spawn(['/usr/libexec/at-spi-bus-launcher', '--launch-immediately'], env) + for _ in range(100): + if glob.glob(os.path.join(runtime, 'at-spi', 'bus_*')): + break + time.sleep(0.1) + else: + kill(pids, tmp) + sys.exit('error: accessibility bus did not start') + + env.update(QT_QPA_PLATFORM='xcb', QT_ACCESSIBILITY='1', + QT_LINUX_ACCESSIBILITY_ALWAYS_ON='1', QT_NO_XDG_DESKTOP_PORTAL='1', + QT_QPA_PLATFORMTHEME='generic', # not the KDE file dialog, its KIO jobs crash in this session + LANG='C', LC_ALL='C') + log = open(os.path.join(tmp, 'app.log'), 'w') + appproc, app_pid = launch([app] + args.args, env, tmp, log) + pids.append(appproc.pid) + + state = dict(env={k: env[k] for k in ('XDG_RUNTIME_DIR', 'HOME', 'DBUS_SESSION_BUS_ADDRESS', 'DISPLAY', + 'QT_QPA_PLATFORM', 'QT_ACCESSIBILITY', + 'QT_LINUX_ACCESSIBILITY_ALWAYS_ON', 'QT_NO_XDG_DESKTOP_PORTAL', 'QT_QPA_PLATFORMTHEME', + 'LANG', 'LC_ALL')}, + pids=pids, app_pid=app_pid, app_pgid=appproc.pid, app_cmd=[app] + args.args, tmp=tmp) + os.makedirs(os.path.dirname(STATE), exist_ok=True) + with open(STATE, 'w') as f: + json.dump(state, f) + + # wait for the application to show up on the accessibility bus + os.environ.update(state['env']) + for _ in range(100): + if appproc.poll() is not None: + print(open(os.path.join(tmp, 'app.log')).read()) + kill(pids, tmp) + os.remove(STATE) + sys.exit('error: application exited with code %s' % appproc.returncode) + if find_app(state) is not None: + print('started, display %s, app log %s/app.log' % (env['DISPLAY'], tmp)) + return + time.sleep(0.2) + sys.exit('error: application did not appear on the accessibility bus (see %s/app.log)' % tmp) + + +def restart(args): + """Quit the application and start it again, same HOME so settings are kept.""" + state = load_state() + try: + os.killpg(state['app_pgid'], signal.SIGTERM) + except ProcessLookupError: + pass + time.sleep(1) + env = dict(os.environ) + env.update(state['env']) + log = open(os.path.join(state['tmp'], 'app.log'), 'a') + p, state['app_pid'] = launch(state['app_cmd'], env, state['tmp'], log) + state['app_pgid'] = p.pid + state['pids'].append(p.pid) + with open(STATE, 'w') as f: + json.dump(state, f) + for _ in range(100): + if find_app(state) is not None: + print('restarted') + return + time.sleep(0.2) + sys.exit('error: application did not come back') + + +def kill(pids, tmp): + for pid in reversed(pids): + try: + os.killpg(pid, signal.SIGTERM) + except (ProcessLookupError, PermissionError): + try: + os.kill(pid, signal.SIGTERM) + except (ProcessLookupError, PermissionError): + pass + time.sleep(0.3) + shutil.rmtree(tmp, ignore_errors=True) + + +def stop(args): + try: + state = load_state() + except SystemExit: + return + kill(state['pids'], state['tmp']) + os.remove(STATE) + print('stopped') + + +# ---------------------------------------------------------------- AT-SPI + +_atspi = None + + +def atspi(): + global _atspi + if _atspi is None: + os.environ.update(load_state()['env']) + import gi + gi.require_version('Atspi', '2.0') + from gi.repository import Atspi + _atspi = Atspi + return _atspi + + +def find_app(state): + A = atspi() + desktop = A.get_desktop(0) + for i in range(desktop.get_child_count()): + try: + app = desktop.get_child_at_index(i) + if app is not None and app.get_process_id() == state['app_pid']: + return app + except Exception: + pass + return None + + +def app_running(state): + """The process is there (a zombie is a crashed application nobody has collected).""" + try: + with open('/proc/%d/stat' % state['app_pid']) as f: + return f.read().rsplit(')', 1)[1].split()[0] != 'Z' + except OSError: + return False + + +def get_app(): + state = load_state() + for _ in range(25): # the application does not answer on the accessibility bus while a modal dialog opens + app = find_app(state) + if app is not None: + return app + if not app_running(state): + break + time.sleep(0.2) + if app_running(state): + sys.exit('error: the application is running but does not answer on the accessibility bus (busy or hung?)') + sys.exit('error: the application is not running%s. log: %s/app.log' % (exit_info(state) or ' (crashed?)', state['tmp'])) + + +def children(node): + try: + for i in range(node.get_child_count()): + c = node.get_child_at_index(i) + if c is not None: + yield i, c + except Exception: + return + + +def states(node): + A = atspi() + st = node.get_state_set() + names = [] + for label, s in (('showing', A.StateType.SHOWING), ('enabled', A.StateType.ENABLED), + ('focused', A.StateType.FOCUSED), ('checked', A.StateType.CHECKED), + ('selected', A.StateType.SELECTED), ('expanded', A.StateType.EXPANDED), + ('editable', A.StateType.EDITABLE), ('modal', A.StateType.MODAL)): + if st.contains(s): + names.append(label) + return names + + +def walk(node, path=(), depth=0): + yield path, node, depth + for i, c in children(node): + yield from walk(c, path + (i,), depth + 1) + + +def node_text(node): + # the interface functions take the accessible as first argument, node.get_text_iface() does not work here + A = atspi() + try: + if 'Text' in A.Accessible.get_interfaces(node): + return A.Text.get_text(node, 0, min(A.Text.get_character_count(node), 2000)) + except Exception: + pass + return None + + +def node_value(node): + A = atspi() + try: + if 'Value' in A.Accessible.get_interfaces(node): + return A.Value.get_current_value(node) + except Exception: + pass + return None + + +def extents(node): + A = atspi() + try: + e = node.get_component_iface().get_extents(A.CoordType.SCREEN) + return e.x, e.y, e.width, e.height + except Exception: + return None + + +def describe(path, node, with_geom=True): + name = node.get_name() or '' + s = '%s %s %r' % ('.'.join(map(str, path)) or '-', node.get_role_name(), name) + text = node_text(node) + if text and text != name: + s += ' text=%r' % (text if len(text) < 200 else text[:200] + '...') + val = node_value(node) + if val is not None: + s += ' value=%s' % val + st = states(node) + if st: + s += ' [%s]' % ','.join(st) + g = extents(node) + if with_geom and g and g[2] > 0: + s += ' @%d,%d %dx%d' % g + return s + + +def cmd_tree(args): + app = get_app() + root, rootpath = app, () + if args.path: + rootpath = tuple(int(x) for x in args.path.split('.')) + for i in rootpath: + root = root.get_child_at_index(i) + for path, node, depth in walk(root, rootpath): + d = len(path) - len(rootpath) + if d > args.depth: + continue + if args.showing and 'showing' not in states(node) and d > 0: + continue + print(' ' * d + describe(path, node)) + + +def cmd_windows(args): + app = get_app() + for i, c in children(app): + print(describe((i,), c)) + + +def cmd_texts(args): + """All visible text on screen, one line per element, for checking EXPECTED.""" + app = get_app() + for path, node, depth in walk(app): + if 'showing' not in states(node): + continue + role = node.get_role_name() + name = node.get_name() or '' + text = node_text(node) + if role in ('label', 'push button', 'check box', 'radio button', 'menu item', 'text', 'entry', + 'page tab', 'combo box', 'table cell', 'tree item', 'list item', 'status bar', + 'title bar', 'spin button', 'frame', 'dialog', 'alert') or text: + line = '%s %s %r' % ('.'.join(map(str, path)), role, name) + if text and text != name: + line += ' text=%r' % text + if 'checked' in states(node): + line += ' [checked]' + print(line) + + +# ---------------------------------------------------------------- finding + +def select(args, need_showing=True): + app = get_app() + if args.path: + node = app + for i in (int(x) for x in args.path.split('.')): + node = node.get_child_at_index(i) + if node is None: + sys.exit('error: no such path') + return node + matches = [] + for path, node, depth in walk(app): + role = node.get_role_name() + if args.role and args.role.lower() != role.lower(): + continue + name = node.get_name() or '' + text = '' # found by name only: the text of a panel would match the message of an error + if args.name is not None: + want = args.name.lower() + if args.exact: + if name.lower() != want and text.lower() != want: + continue + elif want not in name.lower() and want not in text.lower(): + continue + elif not args.role: + sys.exit('error: give --name, --role or --path') + if need_showing and 'showing' not in states(node): + continue + matches.append((path, node)) + if not matches: + sys.exit('error: no matching visible element (name=%r role=%r); try "tree --showing"' % (args.name, args.role)) + if args.nth is None and len(matches) > 1: + lines = '\n'.join(' ' + describe(p, n) for p, n in matches[:15]) + sys.exit('error: %d matches, narrow with --role/--exact or pick with --nth N (0-based):\n%s' % (len(matches), lines)) + nth = args.nth or 0 + if nth >= len(matches): + sys.exit('error: no matching visible element: only %d match(es) for name=%r role=%r, nth=%d is too high' + % (len(matches), args.name, args.role, nth)) + return matches[nth][1] + + +def xdo(*a): + env = dict(os.environ) + env.update(load_state()['env']) + subprocess.run(['xdotool'] + [str(x) for x in a], env=env, check=True) + + +def center(node): + g = extents(node) + if not g or g[2] <= 0: + sys.exit('error: element has no geometry') + return g[0] + g[2] // 2, g[1] + g[3] // 2 + + +def mouse(node, button=1, count=1): + x, y = center(node) + xdo('mousemove', x, y) + time.sleep(0.05) + xdo('click', '--repeat', count, '--delay', 80, button) + + +def do_action(node): + A = atspi() + try: + act = node.get_action_iface() + if act is None: + return False + for i in range(act.get_n_actions()): + if act.get_action_name(i).lower() in ('click', 'press', 'toggle', 'activate', 'check', 'uncheck'): + return act.do_action(i) + except Exception: + pass + return False + + +def cmd_click(args): + node = select(args) + desc = describe((), node, False) + if args.mouse: + mouse(node) + else: + # the action of a button that opens a modal dialog does not return until the dialog is closed + done = [] + t = threading.Thread(target=lambda: done.append(do_action(node)), daemon=True) + t.start() + t.join(args.settle + 1) + if done == [False]: + mouse(node) + elif not done: + time.sleep(args.settle) + print('clicked ' + desc, flush=True) + os._exit(0) + time.sleep(args.settle) + print('clicked ' + desc) + + +def cmd_rightclick(args): + node = select(args) + mouse(node, 3) + time.sleep(args.settle) + print('right-clicked ' + describe((), node, False)) + + +def cmd_doubleclick(args): + node = select(args) + mouse(node, 1, 2) + time.sleep(args.settle) + print('double-clicked ' + describe((), node, False)) + + +def cmd_focus(args): + node = select(args) + ok = node.get_component_iface().grab_focus() + if not ok: + mouse(node) + print('focused ' + describe((), node, False)) + + +def cmd_settext(args): + node = select(args) + et = node.get_editable_text_iface() + if et is None: + sys.exit('error: element is not editable text') + et.set_text_contents(args.value) + time.sleep(args.settle) + print('set text of ' + describe((), node, False)) + + +def cmd_type(args): + xdo('type', '--delay', 20, '--', args.value) + time.sleep(args.settle) + + +def cmd_key(args): + xdo('key', *args.keys) + time.sleep(args.settle) + + +def cmd_hover(args): + x, y = center(select(args)) + xdo('mousemove', x, y) + + +def cmd_wait(args): + deadline = time.time() + args.timeout + while True: + try: + node = select(args) + found = describe((), node, False) + except SystemExit as e: + if 'matches' in str(e): # ambiguous means it exists + found = str(e).split('\n')[0] + else: + found = None + if args.gone != (found is not None): + print('gone' if args.gone else 'found ' + found) + return + if time.time() > deadline: + sys.exit('error: timeout waiting for element %s' % ('to disappear' if args.gone else 'to appear')) + time.sleep(0.3) + + +def cmd_info(args): + """One line about the element: role, name, text, value and [states].""" + print(describe((), select(args), False)) + + +def cmd_suggest(args): + """Visible named elements, the ones most like --name first (for error messages).""" + import difflib + want = (args.name or '').lower() + items = [] + for path, node, depth in walk(get_app()): + name = (node.get_name() or '').strip() + if name and 'showing' in states(node): + items.append((node.get_role_name(), name)) + items.sort(key=lambda it: ((it[0].lower() != (args.role or '').lower()) if args.role else 0, + -difflib.SequenceMatcher(None, want, it[1].lower()).ratio())) + seen = [] + for it in items: + if it not in seen: + seen.append(it) + print('%s %r' % it) + if len(seen) >= args.limit: + break + + +def cmd_screenshot(args): + from PIL import Image + state = load_state() + src = os.path.join(state['tmp'], 'Xvfb_screen0') + data = open(src, 'rb').read() + (hdr, _ver, _fmt, _depth, w, h, _xoff, _bo, _bu, _bbo, _bp, bpp, bpl, _vc, + rm, gm, bm, _bpr, _ncm, ncolors) = struct.unpack('>20I', data[:80]) + off = hdr + ncolors * 12 + img = Image.frombuffer('RGB', (w, h), data[off:off + bpl * h], 'raw', 'BGRX', bpl, 1) + img.save(args.file) + print('saved %s (%dx%d)' % (args.file, w, h)) + + +def cmd_ocr(args): + """Text on the screen read by tesseract, also what AT-SPI does not show (some context menus).""" + import tempfile + png = os.path.join(tempfile.mkdtemp(prefix='guitest-ocr-'), 'screen.png') + args.file = png + with contextlib.redirect_stdout(io.StringIO()): + cmd_screenshot(args) + from PIL import Image + img = Image.open(png) + img = img.resize((img.width * 2, img.height * 2), Image.LANCZOS) + img.save(png) + p = subprocess.run(['tesseract', png, '-', '--psm', '11'], capture_output=True, text=True) + if p.returncode: + sys.exit('error: tesseract failed: ' + p.stderr.strip()) + shutil.rmtree(os.path.dirname(png), ignore_errors=True) + print(p.stdout) + + +def cmd_log(args): + print(open(os.path.join(load_state()['tmp'], 'app.log')).read()) + + +def cmd_alive(args): + sys.exit(0 if app_running(load_state()) else 'application is not running') + + +# ---------------------------------------------------------------- cli + +def selector(p): + p.add_argument('--name', help='substring of accessible name or text (case-insensitive)') + p.add_argument('--role', help='role, e.g. "push button", "menu item", "tree item", "check box"') + p.add_argument('--path', help='path from "tree", e.g. 0.1.2') + p.add_argument('--nth', type=int, help='pick the Nth match (0-based)') + p.add_argument('--exact', action='store_true', help='--name must match exactly') + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + sub = ap.add_subparsers(dest='cmd', required=True) + + p = sub.add_parser('start', help='start session and application') + p.add_argument('app') + p.add_argument('args', nargs='*') + p.add_argument('--home', help='HOME for the application (default: empty temporary dir)') + p.add_argument('--size', default='1280x900') + p.set_defaults(f=start) + + sub.add_parser('stop', help='stop session').set_defaults(f=stop) + sub.add_parser('restart', help='quit and start the application again (settings are kept)').set_defaults(f=restart) + sub.add_parser('alive', help='exit 0 if the application is running').set_defaults(f=cmd_alive) + sub.add_parser('log', help='application stdout/stderr').set_defaults(f=cmd_log) + sub.add_parser('windows', help='top level windows and dialogs').set_defaults(f=cmd_windows) + sub.add_parser('texts', help='all visible texts').set_defaults(f=cmd_texts) + + p = sub.add_parser('tree', help='accessibility tree') + p.add_argument('--depth', type=int, default=99) + p.add_argument('--path', help='only this subtree') + p.add_argument('--showing', action='store_true', help='only visible elements') + p.set_defaults(f=cmd_tree) + + for name, f, h in (('click', cmd_click, 'click (uses the accessibility action when there is one)'), + ('rightclick', cmd_rightclick, 'right click (opens context menu)'), + ('doubleclick', cmd_doubleclick, 'double click'), + ('focus', cmd_focus, 'give keyboard focus'), + ('hover', cmd_hover, 'move the mouse over the element')): + p = sub.add_parser(name, help=h) + selector(p) + p.add_argument('--settle', type=float, default=0.5, help='seconds to wait afterwards') + if name == 'click': + p.add_argument('--mouse', action='store_true', help='use a real mouse click') + p.set_defaults(f=f) + + p = sub.add_parser('settext', help='replace the text of an edit field') + selector(p) + p.add_argument('value') + p.add_argument('--settle', type=float, default=0.3) + p.set_defaults(f=cmd_settext) + + p = sub.add_parser('type', help='type text with the keyboard into the focused element') + p.add_argument('value') + p.add_argument('--settle', type=float, default=0.3) + p.set_defaults(f=cmd_type) + + p = sub.add_parser('key', help='press keys, xdotool syntax, e.g. Return ctrl+o Escape') + p.add_argument('keys', nargs='+') + p.add_argument('--settle', type=float, default=0.3) + p.set_defaults(f=cmd_key) + + p = sub.add_parser('wait', help='wait until a (visible) element exists') + selector(p) + p.add_argument('--timeout', type=float, default=10) + p.add_argument('--gone', action='store_true', help='wait until it disappears') + p.set_defaults(f=cmd_wait) + + p = sub.add_parser('info', help='role, name, text, value and states of one element') + selector(p) + p.set_defaults(f=cmd_info) + + p = sub.add_parser('suggest', help='visible elements most similar to --name/--role') + p.add_argument('--name') + p.add_argument('--role') + p.add_argument('--limit', type=int, default=8) + p.set_defaults(f=cmd_suggest) + + p = sub.add_parser('ocr', help='print the text on the screen as read by tesseract (for what "texts" does not show)') + p.set_defaults(f=cmd_ocr) + p = sub.add_parser('screenshot', help='save a PNG of the screen') + p.add_argument('file') + p.set_defaults(f=cmd_screenshot) + + args = ap.parse_args() + try: # command history next to the state file, so a watcher can see what a test is doing + with open(os.path.join(os.path.dirname(STATE), 'commands.log'), 'a') as f: + f.write(time.strftime('%H:%M:%S ') + ' '.join(sys.argv[1:]) + '\n') + except OSError: + pass + args.f(args) + + +if __name__ == '__main__': + main() diff --git a/gui/manualtest/mainwindow.md b/gui/manualtest/mainwindow.md deleted file mode 100644 index 563a538530f..00000000000 --- a/gui/manualtest/mainwindow.md +++ /dev/null @@ -1,21 +0,0 @@ - -# Main window - -Some manual testing in the main window interface - - -## Test: Recheck file - -1. Load a project with results. -1. Restart the GUI. -1. Right click on a file and click on "Recheck". - -EXPECTED: Results for that file should be refreshed. - - -## Test: Switch project - -1. Open project #1 -1. Open project #2 that has different results - -EXPECTED: Results for project #1 should go away, results for project #2 should be shown diff --git a/gui/manualtest/projectfiledialog.md b/gui/manualtest/projectfiledialog.md deleted file mode 100644 index ce7d39b1df0..00000000000 --- a/gui/manualtest/projectfiledialog.md +++ /dev/null @@ -1,52 +0,0 @@ - -# Project file dialog - -Some manual testing in the project file dialog interface - - -## Test: Relative paths - -Ticket: #14983 - -1. Configure files/paths in project folder: - * import a projectfile - * add include paths in project folder - * exclude file/folder - -2. Save project - -EXPECTED: Relative paths should be used in the XML - - -## Test: Platform file pic8.xml - -Ticket: #14489 - -EXPECTED: In the project file dialog it should be possible to select xml files i.e. pic8.xml - -TODO: can this test be automated - - -## Test: Custom cfg file - -Ticket: #14672 - -1. Copy addons/avr.cfg to a file "aa.cfg" in same folder as a cppcheck GUI project file - -EXPECTED: It should not be possible to activate "aa.cfg" in the project file dialog, it should appear in alphabetical order. - - -## Test: Misra C checkbox - -Ticket: #14488 - -Matrix: Use both open source and premium - -1. Load project file in trac ticket 14488 -1. Goto "Edit project" -1. Goto "Addons" tab - -EXPECTED: The misra c checkbox should be checked - -TODO: can this test be automated - diff --git a/gui/manualtest/report.py b/gui/manualtest/report.py new file mode 100644 index 00000000000..e84df758eb9 --- /dev/null +++ b/gui/manualtest/report.py @@ -0,0 +1,408 @@ +"""The HTML report of a run.py run: summary, results per test file, history over the earlier runs, +the failed tests with their screenshots and all tests. One file, no external resources. + +write(path, run, history) where run is what run.py saves as results.json and history is the +results.json of earlier runs of the same edition, oldest first. +""" + +import html +import os + +STATUSES = ('PASS', 'FAIL', 'SKIP') +ICON = {'PASS': '✓', 'FAIL': '✗', 'SKIP': '–', None: ''} +COLOR = {'PASS': 'var(--pass)', 'FAIL': 'var(--fail)', 'SKIP': 'var(--skip)'} +HISTORY_RUNS = 15 # columns in the history, including this run + +CSS = """ +:root { + --ink: #1b2330; --ink-2: #4a5568; --muted: #6b7686; --line: #e2e6ec; --line-2: #cfd6df; --paper: #fff; --band: #f7f8fa; + --accent: #2456c8; --accent-soft: #e8eefb; + --pass: #0ca30c; --pass-bg: #e4f5e4; --pass-ink: #0b5a0b; + --fail: #d03b3b; --fail-bg: #fbe6e6; --fail-ink: #9a1d1d; + --skip: #fab219; --skip-bg: #fdf2d6; --skip-ink: #6e4b00; + --none: #dfe3e9; + --sans: system-ui, -apple-system, "Segoe UI", Ubuntu, Cantarell, Roboto, sans-serif; + --mono: ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace; + color-scheme: light; +} +* { box-sizing: border-box; } +body { margin: 0; background: var(--paper); color: var(--ink); font: 14px/1.5 var(--sans); } +main { max-width: 72rem; margin: 0 auto; padding: 1.6rem max(16px, 2.2rem) 3rem; } +h1, h2, h3 { margin: 0; text-wrap: balance; } +a { color: var(--accent); } +section { margin-top: 2rem; } +h2 { font-size: 1.02rem; font-weight: 650; margin-bottom: .2rem; } +.sub { color: var(--muted); font-size: .85rem; margin: 0 0 .8rem; } +.rhead { display: flex; gap: 1rem 2rem; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; } +.rhead h1 { font-size: 1.35rem; font-weight: 650; } +.eyebrow { text-transform: uppercase; letter-spacing: .08em; font-size: .72rem; color: var(--muted); font-weight: 600; } +.verdict { border-radius: 8px; padding: .45rem .85rem; font-weight: 650; } +.verdict.FAIL { background: var(--fail-bg); color: var(--fail-ink); } +.verdict.PASS { background: var(--pass-bg); color: var(--pass-ink); } +.meta { display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: .55rem 1.5rem; margin: 1.1rem 0 0; + padding-top: 1rem; border-top: 1px solid var(--line); } +.meta div { min-width: 0; } +.meta dt { font-size: .75rem; color: var(--muted); } +.meta dd { margin: 0; overflow-wrap: anywhere; } +.meta dd.mono { font-family: var(--mono); font-size: .85rem; } +.dirty, .aborted { color: var(--fail-ink); } +.dirty { font-size: .75rem; margin-left: .3rem; } +.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: .8rem; margin-top: 1.4rem; } +.tile { border: 1px solid var(--line); border-radius: 8px; padding: .75rem .9rem; } +.tile .lbl { font-size: .8rem; color: var(--muted); display: flex; align-items: center; gap: .4rem; } +.tile .val { font-size: 1.7rem; font-weight: 650; line-height: 1.2; font-variant-numeric: tabular-nums; } +.tile .note { font-size: .78rem; color: var(--muted); } +.dot { width: .6rem; height: .6rem; border-radius: 50%; display: inline-block; flex: none; } +.d-PASS { background: var(--pass); } .d-FAIL { background: var(--fail); } .d-SKIP { background: var(--skip); } +.chip { display: inline-block; border-radius: 5px; padding: 0 .45rem; font-size: .75rem; font-weight: 650; line-height: 1.6; white-space: nowrap; } +.c-PASS { background: var(--pass-bg); color: var(--pass-ink); } +.c-FAIL { background: var(--fail-bg); color: var(--fail-ink); } +.c-SKIP { background: var(--skip-bg); color: var(--skip-ink); } +.c-CRASH { background: var(--fail-ink); color: #fff; } +.c-NEW { background: var(--accent-soft); color: var(--accent); } +.legend { display: flex; gap: 1rem; flex-wrap: wrap; font-size: .8rem; color: var(--ink-2); margin: .2rem 0 .5rem; } +.legend span { display: inline-flex; align-items: center; gap: .35rem; } +.chart { overflow-x: auto; } +.chart svg { display: block; width: 100%; height: auto; min-width: 34rem; } +.chart text { font-family: var(--sans); fill: var(--ink-2); font-size: 12px; } +.chart text.m { fill: var(--muted); font-size: 11px; } +.chart text.b { fill: var(--ink); font-weight: 600; } +.chart text.sha { font-family: var(--mono); font-size: 9.5px; fill: var(--muted); } +.chart .grid { stroke: var(--line); stroke-width: 1; } +.cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1.5rem 2.5rem; } +.cols > * { min-width: 0; } +.changes { display: grid; gap: .45rem; } +.changes div { display: flex; gap: .5rem; align-items: baseline; flex-wrap: wrap; } +.file { color: var(--muted); font-size: .8rem; } +.tbl { overflow-x: auto; } +table.tests { border-collapse: collapse; width: 100%; min-width: 40rem; } +.tests th { text-align: left; font-size: .75rem; color: var(--muted); font-weight: 600; padding: .35rem .6rem; border-bottom: 1px solid var(--line-2); } +.tests td { padding: .45rem .6rem; border-bottom: 1px solid var(--line); vertical-align: top; } +.tests tr.grp td { background: var(--band); font-weight: 600; font-size: .82rem; color: var(--ink-2); padding-top: .55rem; } +.tests tr.grp span { font-weight: 400; color: var(--muted); } +.tests td.t { white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; color: var(--ink-2); } +.tests td.links { white-space: nowrap; font-size: .82rem; } +.tests details { margin-top: .3rem; font-size: .82rem; } +.tests summary { color: var(--muted); cursor: pointer; } +.reason { font-family: var(--mono); font-size: .8rem; color: var(--ink-2); white-space: pre-wrap; overflow-wrap: anywhere; } +.strip { display: flex; gap: 2px; width: 11rem; margin-top: .35rem; } +.strip i { flex: 1; height: .5rem; background: var(--pass); } +.strip i:first-child { border-radius: 3px 0 0 3px; } .strip i:last-child { border-radius: 0 3px 3px 0; } +.strip i.f { background: var(--fail); } .strip i.n { background: var(--none); } +.stripnote { font-size: .75rem; color: var(--muted); } +.card { border: 1px solid var(--line); border-left: 4px solid var(--fail); border-radius: 8px; padding: .9rem 1rem; display: grid; gap: .5rem; } +.card + .card { margin-top: .8rem; } +.card.SKIP { border-left-color: var(--skip); } +.card-head { display: flex; gap: .5rem; align-items: baseline; flex-wrap: wrap; } +.card-head h3 { font-size: .98rem; } +.card-head .links { margin-left: auto; font-size: .82rem; } +.film { display: flex; gap: .6rem; overflow-x: auto; padding-bottom: .3rem; } +.shot { flex: none; width: 10.5rem; font-size: .75rem; color: var(--ink-2); text-decoration: none; } +.shot:hover b { text-decoration: underline; } +.shot img { display: block; width: 100%; aspect-ratio: 4/3; object-fit: cover; object-position: left top; + border: 1px solid var(--line-2); border-radius: 5px; background: var(--band); } +.shot.bad img { outline: 3px solid var(--fail); outline-offset: -1px; } +.shot b { color: var(--ink); } +.film.small .shot { width: 7.5rem; } +table.hgrid { border-collapse: separate; border-spacing: 3px; } +.hgrid th.name { text-align: right; padding-right: .6rem; font-size: .82rem; font-weight: 500; white-space: nowrap; } +.hgrid th.run { font-size: .68rem; color: var(--muted); font-weight: 500; text-align: center; } +.hgrid th.run.cur { color: var(--ink); font-weight: 700; } +.hgrid td.cell { width: 1.6rem; height: 1.35rem; border-radius: 3px; text-align: center; font-size: .72rem; font-weight: 700; color: #fff; background: var(--band); } +.hgrid td.PASS { background: var(--pass); } .hgrid td.FAIL { background: var(--fail); } +.hgrid td.SKIP { background: var(--skip); color: var(--skip-ink); } +.hgrid td.cur { box-shadow: 0 0 0 2px var(--paper), 0 0 0 3.5px var(--ink); } +.hgrid td.note { font-size: .78rem; color: var(--muted); padding-left: .7rem; white-space: nowrap; } +@media (max-width: 520px) { .card-head .links { margin-left: 0; width: 100%; } } +""" + +e = html.escape + + +def dur(s): + s = int(round(s)) + return '%d min %d s' % (s // 60, s % 60) if s >= 60 else '%d s' % s + + +def chip(status): + return '%s %s' % (status, ICON[status], status) + + +def crash_chip(r): + return ' CRASH' if r['crash'] else '' + + +def count(results, status): + return sum(1 for r in results if r['status'] == status) + + +def short_date(started): + return '%d/%d' % (int(started[5:7]), int(started[8:10])) + + +def links(r, outdir): + """Links to the files of a test that exist (a skipped test has none).""" + out = [(r['slug'] + ext, text) for ext, text in (('.txt', 'steps'), ('.app.log', 'app log'), ('.png', 'screenshot')) + if os.path.exists(os.path.join(outdir, r['slug'] + ext))] + return ' · '.join('%s' % (e(f), t) for f, t in out) + + +def strip(r): + """One cell per step: green passed, red failed, grey not run.""" + n, at = r.get('steps', 0), r.get('failed_step', 0) + if not n: + return '' + if r['status'] == 'SKIP': + cells, note = ['n'] * n, 'not run' + elif r['status'] == 'FAIL' and not at and not r['crash']: + cells, note = ['n'] * n, 'did not start' + else: + cells = ['' if not at or k < at else 'f' if k == at else 'n' for k in range(1, n + 1)] + note = 'failed at step %d of %d' % (at, n) if at else '%d of %d steps' % (n, n) + return '
%s
%s
' % ( + note, ''.join('' % c for c in cells), note) + + +def hbar(x, y, w, h, r, fill, tip): + """A horizontal bar, rounded at the data end only.""" + r = min(r, w / 2, h / 2) + return ('%s' + % (x, y, w - r, r, r, r, r, h - 2 * r, r, r, r, r, w - r, fill, e(tip))) + + +def vbar(x, base, w, h, r, fill, tip, top): + """A column segment; the top segment is rounded at the data end.""" + if not top: + return '%s' % (x, base - h, w, h, fill, e(tip)) + r = min(r, w / 2, h) + return ('%s' + % (x, base, h - r, r, r, r, r, w - 2 * r, r, r, r, r, h - r, fill, e(tip))) + + +def legend(): + return '
%s
' % ''.join('%s' % (k, k) for k in STATUSES) + + +def per_file_chart(run): + rows = [] + for f in run['files']: + rs = [r for r in run['results'] if r['file'] == f['file']] + if rs: + rows.append(dict({k: count(rs, k) for k in STATUSES}, title=f['title'] or f['file'], n=len(rs))) + big = max(r['n'] for r in rows) + step = 1 if big <= 10 else 5 if big <= 50 else 20 + top = -(-big // step) * step + left, width, rh, h = 210, 900, 30, 18 + bw = width - left - 70 + sx = lambda v: v / top * bw + s = [] + for v in range(0, top + 1, step): + s.append('%d' + % (left + sx(v), left + sx(v), len(rows) * rh + 4, left + sx(v), len(rows) * rh + 20, v)) + for i, r in enumerate(rows): + y = 8 + i * rh + title = r['title'] if len(r['title']) <= 30 else r['title'][:29] + '…' + s.append('%s%s' % (left - 10, y + h / 2 + 4, e(title), e(r['title']))) + x = left + segs = [k for k in STATUSES if r[k]] + for j, k in enumerate(segs): + last = j == len(segs) - 1 + w = sx(r[k]) - (0 if last else 2) # a 2px gap between the segments + tip = '%s\n%d %s of %d tests' % (r['title'], r[k], k, r['n']) + s.append(hbar(x, y, w, h, 4, COLOR[k], tip) if last else + '%s' % (x, y, w, h, COLOR[k], e(tip))) + x += sx(r[k]) + s.append('%d/%d' % (x + 8, y + h / 2 + 4, r['PASS'], r['n'])) + return ('
%s
' + % (width, len(rows) * rh + 28, ''.join(s))) + + +def trend_chart(runs): + width, left, base, top = 900, 34, 190, 14 + n = len(runs) + bw = min(30, (width - left - 30) / n * 0.6) + gap = min((width - left - 20 - n * bw) / max(n - 1, 1), 30) + big = max(len(r['results']) for r in runs) or 1 + step = 5 if big <= 25 else 10 if big <= 60 else 50 + ymax = -(-big // step) * step + sy = lambda v: v / ymax * (base - top) + s = [] + for v in range(0, ymax + 1, step): + s.append('%d' + % (left, width - 10, base - sy(v), base - sy(v), left - 6, base - sy(v) + 4, v)) + for i, run in enumerate(runs): + x, cur = left + 6 + i * (bw + gap), i == n - 1 + c = {k: count(run['results'], k) for k in STATUSES} + tip = 'Run %s · %s%s\n%d pass · %d fail · %d skip' % (run['started'][:16].replace('T', ' '), run['sha'][:8], + ' (aborted)' if run.get('aborted') else '', c['PASS'], c['FAIL'], c['SKIP']) + y0 = base + segs = [k for k in STATUSES if c[k]] + for j, k in enumerate(segs): + last = j == len(segs) - 1 + s.append(vbar(x, y0, bw, sy(c[k]) - (0 if last else 2), 4, COLOR[k], tip, last)) + y0 -= sy(c[k]) + if c['FAIL']: + s.append('%d' % (x + bw / 2, y0 - 6, c['FAIL'])) + s.append('%s' % ('b' if cur else 'm', x + bw / 2, base + 16, short_date(run['started']))) + s.append('%s' % (x + bw / 2, base + 30, e(run['sha'][:6]))) + if cur: + s.append('this run' % (x + bw / 2, base + 46)) + return ('
%s
' + % (width, base + 52, ''.join(s))) + + +def describe_history(h, runs): + """What a row of the status history says: flaky, failing since, fixed since...""" + seen = [x for x in h if x] + last = h[-1] + changes = sum(1 for a, b in zip(seen, seen[1:]) if a != b) + since = len(h) - 1 + while since > 0 and h[since - 1] == last: + since -= 1 + when = '%s %s (%s)' % (short_date(runs[since]['started']), runs[since]['started'][11:16], runs[since]['sha'][:6]) + if all(x == 'SKIP' for x in seen): + return 'never ran' + if changes >= 4: + return 'flaky: %d changes' % changes + if last == 'FAIL': + return 'new failure' if since == len(h) - 1 else 'failing since ' + when + if last == 'SKIP': + return 'skipped since ' + short_date(runs[since]['started']) + return 'fixed since ' + when if last == 'PASS' else '' + + +def history_sections(run, history): + runs = history[-(HISTORY_RUNS - 1):] + [run] + status = [{r['name']: r['status'] for r in x['results']} for x in runs] + prev = status[-2] + cur = run['results'] + new = [r for r in cur if r['status'] == 'FAIL' and prev.get(r['name']) != 'FAIL'] + fixed = [r for r in cur if r['status'] == 'PASS' and prev.get(r['name']) == 'FAIL'] + still = [r for r in cur if r['status'] == 'FAIL' and prev.get(r['name']) == 'FAIL'] + line = lambda r, tag: '
%s%s%s
' % (tag, e(r['name']), e(r['file'])) + changes = ''.join([line(r, 'NEW ' + chip('FAIL')) for r in new] + + [line(r, chip('PASS')) for r in fixed] + + [line(r, '✗ STILL FAILING') for r in still]) or \ + '

No test changed between PASS and FAIL.

' + p = runs[-2] + out = ['

The last %d runs

%s. The number on a column is the failed tests.

%s%s
' + % (len(runs), e(run['edition'] or 'All editions'), legend(), trend_chart(runs)), + '

Changed since the previous run

Run %s · %s

%s
' + % (e(p['started'][:16].replace('T', ' ')), e(p['sha'][:10]), changes)] + rows = [r for r in cur if any(s.get(r['name'], 'PASS') != 'PASS' for s in status)] + if rows: + head = '%s' % ''.join( + '%s' % (' cur' if i == len(runs) - 1 else '', short_date(x['started'])) for i, x in enumerate(runs)) + body = [] + for r in rows: + h = [s.get(r['name']) for s in status] + cells = ''.join('%s' % ( + x or '', ' cur' if i == len(runs) - 1 else '', + e('%s · %s %s: %s' % (r['name'], short_date(runs[i]['started']), runs[i]['sha'][:8], x or 'not in this run')), ICON[x]) + for i, x in enumerate(h)) + body.append('%s%s%s' % (e(r['name']), cells, describe_history(h, runs))) + out.append('

Status history

The tests that did not pass in every run. ' + 'The framed column is this run.

%s%s
' + % (head, ''.join(body))) + return out, len(new) - len(fixed), len(new), len(fixed) + + +def film(r, outdir): + """The screenshots after the last steps before the failure, and the final screenshot.""" + shots = [(s['file'], 'Step %d' % s['step'], s['label'], False) for s in r.get('shots', [])][-3:] + if os.path.exists(os.path.join(outdir, r['slug'] + '.png')): + at = r.get('failed_step', 0) + shots.append((r['slug'] + '.png', 'Step %d · failed here' % at if at else 'At the end', '', True)) + return '
%s
' % ''.join( + '%s%s
%s
' % ( + ' bad' if bad else '', e(f), e(f), e(title), e(title), e(label)) for f, title, label, bad in shots) if shots else '' + + +def problem_cards(run, outdir): + rs = [r for r in run['results'] if r['status'] != 'PASS'] + rs.sort(key=lambda r: r['status'] != 'FAIL') + return ''.join('
%s%s

%s

%s%s
' + '%s
%s
%s
' % ( + r['status'], chip(r['status']), crash_chip(r), e(r['name']), e(r['file']), links(r, outdir), + strip(r) if r['status'] == 'FAIL' else '', e(r['reason']), film(r, outdir) if r['status'] == 'FAIL' else '') + for r in rs) + + +def all_tests(run, outdir): + rows = [] + for f in run['files']: + rs = [r for r in run['results'] if r['file'] == f['file']] + if not rs: + continue + rows.append('%s · %s · %d of %d passed' % ( + e(f['title'] or f['file']), e(f['file']), count(rs, 'PASS'), len(rs))) + for r in rs: + shots = r.get('shots', []) + more = ('
screenshots after the steps (%d)
%s
' % ( + len(shots), ''.join('Step %d
%s
' % ( + e(s['file']), e(s['file']), s['step'], e(s['label'])) for s in shots))) if shots else '' + rows.append('%s%s
%s
%s%s%s%s' % ( + chip(r['status']), crash_chip(r), e(r['name']), strip(r), more, + dur(r['seconds']) if r['status'] != 'SKIP' else '–', links(r, outdir))) + return ('
' + '%s
ResultTestTimeFiles
' % ''.join(rows)) + + +def write(path, run, history): + outdir = os.path.dirname(path) + rs = run['results'] + c = {k: count(rs, k) for k in STATUSES} + ran = c['PASS'] + c['FAIL'] + total = sum(r['seconds'] for r in rs) + crashes = sum(1 for r in rs if r['crash']) + verdict = 'FAIL' if c['FAIL'] else 'PASS' + sha = run['sha'][:8] + text = ('✗ FAILED · %d of %d tests failed' % (c['FAIL'], len(rs)) if c['FAIL'] else + '✓ PASSED · all %d tests passed' % ran if not c['SKIP'] else '✓ PASSED · %d passed, %d skipped' % (ran, c['SKIP'])) + meta = [('Date', e(run['started'][:19].replace('T', ' ')), ''), + ('Edition', e(run['edition'] or 'not given, all tests'), ''), + ('Duration', dur(total), ''), + ('Git SHA', e(run['sha']) + ('+ uncommitted changes' if run['dirty'] else ''), 'mono'), + ('Branch', e(run['branch']), ''), + ('Commit', e(run['subject']), ''), + ('Application', e(run['app']), 'mono'), + ('Host', e(run['host']), ''), + ('Test files', e(', '.join(f['file'] for f in run['files'])), '')] + if run.get('aborted'): + meta.append(('Aborted', 'the run was stopped before all tests were done', '')) + hist, delta, new, fixed = history_sections(run, history) if history else ([], 0, 0, 0) + + def tile(label, value, note, status=''): + return '
%s%s
%s
%s
' % ( + '' % status if status else '', label, value, note) + tiles = [tile('Passed', c['PASS'], '%d %% of the tests' % round(100 * c['PASS'] / len(rs)) if rs else '', 'PASS'), + tile('Failed', c['FAIL'], '%d of them crashed the app' % crashes if crashes else 'no crashes', 'FAIL'), + tile('Skipped', c['SKIP'], 'no script or outdated script' if c['SKIP'] else ' ', 'SKIP'), + tile('Duration', dur(total), 'average %d s per test' % round(total / ran) if ran else ' ')] + if history: + tiles.append(tile('Compared to the previous run', '%+d' % delta if delta else '±0', + '%d new failure%s, %d fixed' % (new, '' if new == 1 else 's', fixed))) + + out = ['', '', + '', + 'GUI test report %s %s' % (e(run['edition'] or ''), e(sha)), '
', + '
GUI test report

%s %s · %s

%s
' % ( + e(os.path.basename(run['app'])), e(run['edition'] or ''), e(sha), verdict, text), + '
%s
' % ''.join('
%s
%s
' % (k, cls, v) for k, v, cls in meta), + '
%s
' % ''.join(tiles)] + if len([f for f in run['files'] if any(r['file'] == f['file'] for r in rs)]) > 1: + out.append('

Results per test file

Which part of the GUI has problems.

%s%s
' + % (legend(), per_file_chart(run))) + out += hist + if c['FAIL'] or c['SKIP']: + out.append('

Failed and skipped tests

The reason, how far the test got, and the screenshots ' + 'after the last steps. The red frame is the screen when the test failed.

%s
' % problem_cards(run, outdir)) + if rs: + out.append('

All tests

Grouped by test file. The strip shows the steps: green passed, ' + 'red failed, grey not run.

%s
' % all_tests(run, outdir)) + if not history: + out.append('

No earlier runs of this edition with results.json were found in %s, ' + 'the history is shown from the next run.

' % e(os.path.dirname(outdir))) + out.append('
') + with open(path, 'w', encoding='utf-8') as f: + f.write('\n'.join(out) + '\n') diff --git a/gui/manualtest/run.py b/gui/manualtest/run.py new file mode 100755 index 00000000000..e8c97111374 --- /dev/null +++ b/gui/manualtest/run.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +"""Run the manual tests in a test file by letting Claude drive the GUI. + + run.py APP FILE.json|FILE.md [--edition opensource|premium] [--test SUBSTRING] [--id ID] [--src DIR] [--out DIR] + +FILE.json is edited with web.py and can have test matrices, see testfile.py. FILE.md has +sections "## Test: NAME". For each test (and matrix combination) the application is started +fresh (empty HOME) and the script of the test is run with guitest.py commands, see runner.py. +No AI is involved. With --edition APP is that edition and only the tests for it are run. +Tests without a script are SKIP. A step that fails gives PASS -> FAIL with the +step, the reason and the elements that are on the screen. +""" + +import argparse +import os +import re +import datetime +import json +import platform +import signal +import subprocess +import sys +import tempfile +import time + +import report +import runner +import testfile + +HERE = os.path.dirname(os.path.abspath(__file__)) +GUITEST = os.path.join(HERE, 'guitest.py') + +def parse(path, edition=None): + return [dict(t, file=os.path.basename(path)) for t in testfile.expand(path, edition)] + + +CRASH_RE = re.compile(r'exited with status (?!0\b|143\b)\d+|Segmentation fault|SIGSEGV|SIGABRT|Aborted|core dumped|terminate called|' + r'QFATAL|Fatal error|AddressSanitizer|runtime error:|Assertion .* failed', re.I) + + +def screenshot(sub, path): + """Save a screenshot without the empty (black) part of the screen right of and below the windows. + The top left corner stays, so positions in the image are screen positions.""" + if sub('screenshot', path, capture_output=True).returncode: + return False + try: + from PIL import Image + img = Image.open(path) + box = img.convert('RGB').getbbox() + if box and (box[2] < img.width or box[3] < img.height): + img.crop((0, 0, box[2], box[3])).save(path) + except (ImportError, OSError): + pass + return True + + +def detect_crash(sub, outdir, slug): + """Return a description when the application died or logged a fatal error, else ''.""" + log = sub('log', capture_output=True).stdout + with open(os.path.join(outdir, slug + '.app.log'), 'w') as f: + f.write(log) + hits = [l.strip() for l in log.splitlines() if CRASH_RE.search(l)] + dead = sub('alive', capture_output=True).returncode != 0 + if dead and re.search(r'application exited with status 0\b', log): + dead = False # the test closed the application (Quit) + if not dead and not hits: + return '' + why = 'application is not running at the end of the test' if dead else 'fatal message in application log' + return why + (': ' + hits[0] if hits else '') + + +def emit(kind, **data): + """One line of progress for the web interface, with --events.""" + if EVENTS: + print('@' + json.dumps(dict(data, kind=kind)), flush=True) + + +EVENTS = False + + +def run_test(app, test, outdir, on_step=None): + """Returns {status, reason, crash, steps, failed_step, shots}. failed_step is 0 when no step failed, + shots are the screenshots after the steps: [{step, label, file}].""" + slug = re.sub(r'\W+', '_', test['name']) + shots, reached = [], [0] + result = lambda status, reason, crash='', failed=0: {'status': status, 'reason': reason, 'crash': crash, + 'steps': len(test['script']), 'failed_step': failed, 'shots': shots} + if test.get('stale'): + return result('SKIP', 'OUTDATED SCRIPT: the test was changed after the script was made. ' + 'Generate the script again (gen.py or the web interface) or approve it there.') + if not test['script']: + return result('SKIP', 'no script, make one with gen.py or in the web interface') + work = tempfile.mkdtemp(prefix='guitest-work-') + state = os.path.join(work, 'state.json') + env = dict(os.environ, GUITEST_STATE=state) + sub = lambda *a, **kw: subprocess.run([sys.executable, GUITEST, *a], env=env, text=True, **kw) + try: + variables = testfile.materialize(test['root'], test['projects'], work) # same files every run + except FileNotFoundError as e: + return result('FAIL', str(e)) + r = sub('start', app, capture_output=True) + if r.returncode: + return result('FAIL', 'could not start: ' + (r.stdout + r.stderr).strip()) + try: + with open(state) as f: + home = json.load(f)['env']['HOME'] + def shot(i, label): + name = '%s.step%02d.png' % (slug, i) + if not screenshot(sub, os.path.join(outdir, name)): + return None + shots.append({'step': i, 'label': label, 'file': name}) + return name + def step(i, n, label): + reached[0] = i + if on_step: + on_step(i, n, label) + ok, reason, log = runner.run_script(sub, test['script'], work, home, variables, step, shot) + with open(os.path.join(outdir, slug + '.txt'), 'w') as f: + f.write(log) + screenshot(sub, os.path.join(outdir, slug + '.png')) + crash = detect_crash(sub, outdir, slug) + status = 'PASS' if ok else 'FAIL' + if crash: + # a crash is a failure even if all the steps passed + status, reason = 'FAIL', 'APPLICATION CRASHED: %s\n%s' % (crash, reason) + return result(status, reason, crash, 0 if ok else reached[0]) + finally: + sub('stop', capture_output=True) + + +def git(directory, *a): + r = subprocess.run(['git', '-C', directory, *a], text=True, capture_output=True) + return r.stdout.strip() if r.returncode == 0 else '' + + +def app_info(app, src): + """git info for the tested application, taken from SRC (default: the directory of the binary).""" + d = os.path.abspath(src) if src else os.path.dirname(app) + sha = git(d, 'rev-parse', 'HEAD') + return { + 'path': app, + 'sha': sha or 'unknown', + 'branch': git(d, 'rev-parse', '--abbrev-ref', 'HEAD') or 'unknown', + 'subject': git(d, 'log', '-1', '--format=%s') if sha else '', + 'dirty': bool(git(d, 'status', '--porcelain', '--untracked-files=no')) if sha else False, + } + + +def load_history(out, edition, current): + """results.json of the earlier runs of EDITION in OUT, oldest first.""" + runs = [] + for d in sorted(os.listdir(out)): + path = os.path.join(out, d, 'results.json') + if d == current or not os.path.isfile(path): + continue + try: + with open(path, encoding='utf-8') as f: + run = json.load(f) + except (OSError, ValueError): + continue + if run.get('edition') == edition: + runs.append(run) + return runs[-(report.HISTORY_RUNS - 1):] + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('app') + ap.add_argument('file', nargs='+', help='one or more .json test files') + ap.add_argument('--edition', choices=testfile.EDITIONS, help='the edition that APP is, only the tests for it') + ap.add_argument('--test', help='only tests whose name contains this') + ap.add_argument('--id', help='only the test with this id (all its matrix combinations)') + ap.add_argument('--src', help='git checkout of the application (default: the directory of APP)') + ap.add_argument('--out', default=os.path.join(HERE, 'results')) + ap.add_argument('--events', action='store_true', help='progress lines for the web interface') + args = ap.parse_args() + global EVENTS + EVENTS = args.events + signal.signal(signal.SIGINT, signal.default_int_handler) # also when started in the background + + started = datetime.datetime.now() + info = app_info(os.path.abspath(args.app), args.src) + # each run gets its own directory: results/[-]-/ + parent = args.out + args.out = os.path.join(args.out, '%s%s-%s' % (started.strftime('%Y%m%d-%H%M%S'), + '-' + args.edition if args.edition else '', info['sha'][:8])) + os.makedirs(args.out, exist_ok=True) + failed = 0 + results = [] + aborted = False + try: + selected = [t for f in args.file for t in parse(f, args.edition) + if not (args.test and args.test.lower() not in t['name'].lower() or args.id and t['id'] != args.id)] + emit('start', total=len(selected), out=args.out, edition=args.edition) + for index, test in enumerate(selected, 1): + name = test['name'] + emit('test', index=index, name=name, steps=len(test['script'])) + t0 = time.time() + try: + res = run_test(os.path.abspath(args.app), test, args.out, + lambda k, n, label: emit('step', step=k, steps=n, label=label)) + except KeyboardInterrupt: + results.append({'name': name, 'file': test['file'], 'status': 'SKIP', 'reason': 'aborted by user', 'crash': '', + 'seconds': time.time() - t0, 'slug': re.sub(r'\W+', '_', name), 'steps': 0, 'failed_step': 0, 'shots': []}) + emit('result', **results[-1]) + raise + results.append(dict(res, name=name, file=test['file'], seconds=time.time() - t0, slug=re.sub(r'\W+', '_', name))) + emit('result', **results[-1]) + print('%-4s %s: %s' % (res['status'], name, res['reason']), flush=True) + failed += res['status'] == 'FAIL' + except KeyboardInterrupt: + # the report is written with what is done, tests not started are left out + aborted = True + print('aborted') + run = {'started': started.isoformat(timespec='seconds'), 'edition': args.edition, 'app': info['path'], + 'sha': info['sha'], 'dirty': info['dirty'], 'branch': info['branch'], 'subject': info['subject'], + 'host': '%s, %s' % (platform.node(), platform.platform()), 'aborted': aborted, + 'files': [{'file': os.path.basename(f), 'title': testfile.load(f).get('title', '')} for f in args.file], + 'results': results} + with open(os.path.join(args.out, 'results.json'), 'w', encoding='utf-8') as f: + json.dump(run, f, indent=1) + path = os.path.join(args.out, 'report.html') + report.write(path, run, load_history(parent, args.edition, os.path.basename(args.out))) + print('report: ' + path) + emit('done', report=path) + sys.exit(1 if failed else 0) + + +if __name__ == '__main__': + main() diff --git a/gui/manualtest/runner.py b/gui/manualtest/runner.py new file mode 100644 index 00000000000..11634cdb1e4 --- /dev/null +++ b/gui/manualtest/runner.py @@ -0,0 +1,232 @@ +"""Executes a test script (see testfile.SCRIPT_DOC) with guitest.py commands, no AI involved. + +run_script() gets `sub`, a function that runs one guitest.py command in the session of the test: +sub(cmd, *args, capture_output=True) -> CompletedProcess with returncode, stdout, stderr. +""" + +import os +import re +import shutil +import time + +import testfile + +# actions that change the GUI, a screenshot is taken after each of them +GUI_ACTIONS = ('click', 'rightclick', 'doubleclick', 'focus', 'hover', 'settext', 'type', 'key', 'restart') +NO_ELEMENT = ('no matching visible element', 'does not answer on the accessibility bus') +DEFAULT_TIMEOUT = 5 + + +def selector_args(step): + a = [] + for k in ('name', 'role', 'path'): + if k in step: + a += ['--' + k, str(step[k])] + if 'nth' in step: + a += ['--nth', str(step['nth'])] + if step.get('exact'): + a.append('--exact') + return a + + +def describe(step): + return ' '.join('%s=%r' % (k, v) for k, v in step.items() if k != 'label') + + +def message(p): + text = (p.stderr.strip() or p.stdout.strip() or 'exit code %d' % p.returncode).removeprefix('error: ') + return text.replace('; try "tree --showing"', '') + + +class StepFailed(Exception): + pass + + +def check_path(path, allowed): + path = os.path.realpath(path) + if not any(path == d or path.startswith(d + os.sep) for d in (os.path.realpath(x) for x in allowed)): + raise StepFailed('path %s is outside the test directories' % path) + return path + + +def gui(sub, cmd, step, extra=()): + """Run a command that needs an element. The element may not have appeared yet, so retry.""" + deadline = time.time() + step.get('timeout', DEFAULT_TIMEOUT) + while True: + p = sub(cmd, *selector_args(step), *extra, capture_output=True) + if p.returncode == 0: + return p + if not any(m in message(p) for m in NO_ELEMENT) or time.time() > deadline: + raise StepFailed(message(p)) + time.sleep(0.4) + + +def run_step(sub, step, dirs, captured): + action = step.get('do') or step['expect'] + if 'do' in step: + if action in ('click', 'rightclick', 'doubleclick', 'focus', 'hover'): + gui(sub, action, step, ['--mouse'] if step.get('mouse') else []) + elif action == 'settext': + gui(sub, 'settext', step, [step['value']]) + elif action == 'type': + ok(sub('type', step['value'], capture_output=True)) + elif action == 'key': + keys = step['keys'].split() if isinstance(step['keys'], str) else step['keys'] + ok(sub('key', *keys, capture_output=True)) + elif action == 'wait': + ok(sub('wait', *selector_args(step), '--timeout', str(step.get('timeout', 10)), + *(['--gone'] if step.get('gone') else []), capture_output=True)) + elif action == 'restart': + ok(sub('restart', capture_output=True)) + elif action == 'sleep': + time.sleep(float(step['seconds'])) + elif action == 'copy': + src, dst = check_path(step['from'], dirs), check_path(step['to'], dirs) + if not os.path.isfile(src): + raise StepFailed('file %s does not exist' % src) + os.makedirs(os.path.dirname(dst), exist_ok=True) + shutil.copyfile(src, dst) + elif action == 'write': + path = check_path(step['path'], dirs) + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'w', encoding='utf-8') as f: + f.write(step['content']) + return + if action == 'visible': + ok(sub('wait', *selector_args(step), '--timeout', str(step.get('timeout', DEFAULT_TIMEOUT)), + capture_output=True)) + elif action == 'gone': + ok(sub('wait', *selector_args(step), '--gone', '--timeout', str(step.get('timeout', DEFAULT_TIMEOUT)), + capture_output=True)) + elif action == 'state': + info = gui(sub, 'info', step).stdout + have = set((re.search(r'\[([\w,]*)\]\s*$', info.strip()) or [None, ''])[1].split(',')) + if 'state' in step and step['state'] not in have: + raise StepFailed('element is not %s, its states are [%s]: %s' % (step['state'], ','.join(sorted(have)), info.strip())) + if 'not_state' in step and step['not_state'] in have: + raise StepFailed('element is %s but should not be: %s' % (step['not_state'], info.strip())) + elif action == 'value': + info = gui(sub, 'info', step).stdout.strip() + found = re.findall(r" value=(\S+)", info) + re.findall(r" text='(.*?)'(?= value=| \[|$)", info) + if not any(same(v, step['equals']) for v in found): + raise StepFailed('the value is %s, not %r: %s' % (found or 'missing', step['equals'], info)) + elif action == 'exited': + deadline = time.time() + step.get('timeout', DEFAULT_TIMEOUT) + while sub('alive', capture_output=True).returncode == 0: + if time.time() > deadline: + raise StepFailed('the application is still running') + time.sleep(0.3) + status = re.findall(r'application exited with status (\d+)', sub('log', capture_output=True).stdout) + if status and status[-1] != '0': + raise StepFailed('the application ended with status %s, not 0' % status[-1]) + elif action == 'text': + deadline = time.time() + step.get('timeout', DEFAULT_TIMEOUT) # the screen may take a moment to change + while True: + texts = sub('texts', capture_output=True).stdout + problem = None + if 'contains' in step and step['contains'] not in texts: + problem = 'the text %r is not on the screen' % step['contains'] + elif 'not_contains' in step and step['not_contains'] in texts: + problem = 'the text %r is on the screen but should not be' % step['not_contains'] + if not problem: + break + if time.time() > deadline: + raise StepFailed(problem) + time.sleep(0.4) + elif action == 'ocr': + deadline = time.time() + step.get('timeout', DEFAULT_TIMEOUT) + norm = lambda x: re.sub(r'\s+', ' ', x).lower() + while True: + p = sub('ocr', capture_output=True) + if p.returncode: + raise StepFailed(message(p)) + seen = norm(p.stdout) + problem = None + if 'contains' in step and norm(step['contains']) not in seen: + problem = 'the text %r is not on the screen (OCR)' % step['contains'] + elif 'not_contains' in step and norm(step['not_contains']) in seen: + problem = 'the text %r is on the screen (OCR) but should not be' % step['not_contains'] + if not problem: + break + if time.time() > deadline: + raise StepFailed(problem + '. OCR read: ' + seen[:300]) + time.sleep(0.4) + elif action == 'file': + path = check_path(step['path'], dirs) + try: + with open(path, encoding='utf-8', errors='replace') as f: + text = f.read() + except FileNotFoundError: + raise StepFailed('file %s does not exist' % path) + if 'contains' in step and step['contains'] not in text: + raise StepFailed('%s does not contain %r' % (path, step['contains'])) + if 'not_contains' in step and step['not_contains'] in text: + raise StepFailed('%s contains %r but should not' % (path, step['not_contains'])) + if 'regex' in step and not re.search(step['regex'], text, re.M): + raise StepFailed('%s does not match /%s/' % (path, step['regex'])) + elif action == 'alive': + ok(sub('alive', capture_output=True)) + elif action == 'capture': + info = gui(sub, 'info', step).stdout.strip() + m = re.search(step['regex'], info) + if not m: + raise StepFailed('/%s/ does not match: %s' % (step['regex'], info)) + captured[step['as']] = m.group(1) if m.groups() else m.group(0) + + +def same(a, b): + try: + return float(a) == float(b) # a spin box says 1.0 + except ValueError: + return a == b + + +def ok(p): + if p.returncode: + raise StepFailed(message(p)) + + +def context(sub, step): + """What the screen looks like when a step failed, to make a renamed button easy to find.""" + out = [] + if {'name', 'role', 'path'} & set(step): + cmd = ['suggest'] + ['--%s=%s' % (k, step[k]) for k in ('name', 'role') if k in step] + s = sub(*cmd, capture_output=True).stdout.strip() + if s: + out.append('most similar visible elements:\n' + '\n'.join(' ' + l for l in s.splitlines())) + w = sub('windows', capture_output=True).stdout.strip() + if w: + out.append('windows:\n' + '\n'.join(' ' + l.split(' @')[0] for l in w.splitlines()[:6])) + return '\n '.join(out) + + + +def run_script(sub, script, work, home, variables=None, progress=None, shot=None): + """Returns (ok, message, log). On failure the message names the step and what was on screen. + shot(i, label) is called after each step i that is one of GUI_ACTIONS and returns the name of the + screenshot it saved, or None.""" + dirs = [work, home] + variables = dict(variables or {}, work=work, home=home) + captured = {} # {name} values of the capture steps so far + log = [] + for i, step in enumerate(script, 1): + step = testfile.subst_deep(step, dict(variables, **captured)) + t0 = time.time() + label = step.get('label') + if progress: + progress(i, len(script), label or describe(step)) + head = 'step %d/%d%s: %s' % (i, len(script), ' "%s"' % label if label else '', describe(step)) + try: + run_step(sub, step, dirs, captured) + except StepFailed as e: + log.append('FAIL %s (%.1f s)' % (head, time.time() - t0)) + msg = '%s\n %s' % (head, e) + ctx = context(sub, step) + return False, msg + ('\n ' + ctx if ctx else ''), '\n'.join(log) + '\n' + msg + '\n' + log.append('ok %s (%.1f s)' % (head, time.time() - t0)) + if shot and step.get('do') in GUI_ACTIONS: + time.sleep(0.2) # let the application paint + name = shot(i, label or describe(step)) + if name: + log[-1] += ' screenshot ' + name + return True, 'all %d steps passed' % len(script), '\n'.join(log) + '\n' diff --git a/gui/manualtest/testfile.py b/gui/manualtest/testfile.py new file mode 100644 index 00000000000..71d5b42ac2c --- /dev/null +++ b/gui/manualtest/testfile.py @@ -0,0 +1,404 @@ +#!/usr/bin/env python3 +"""Test files for run.py: JSON (structured, with test matrices) or the older markdown. + +JSON format: + + {"title": "Project file dialog", + "tests": [ + {"name": "Relative paths", + "ticket": "#14983", optional + "enabled": true, optional, default true + "steps": ["Open the project dialog", "Select platform {platform}"], + "expected": "Relative paths are used in the XML", + "todo": "", optional, "cannot be automated" etc + "matrix": {"platform": ["native", "unix32"], "lang": ["C", "C++"]} optional + "editions": ["premium"] optional, default all editions + }]} + +"editions" are the versions of the application that the test is for, a subset of EDITIONS. A run +is for one edition (run.py --edition, with the binary of that edition), the tests that are not for +it are left out. The script is the same for all editions, {edition} in steps/expected/todo/script +is the name of the edition of the run. + +"script" is what run.py executes, see SCRIPT_DOC below. It is written by gen.py (an AI explores +the GUI once) or by hand, and is then replayed without any AI. + +"projects": ["name", ...] are test projects, directories with files and code in DIR/projects/NAME, +shared by all tests next to them. Before every run they are copied to an empty temporary directory, +so a test always starts with the same files: {project:NAME} in a script is the copy of NAME, and +{project} when the test has exactly one project. + +A script is made for the steps, expected result, todo, matrix and test projects (the files in them) of a test. "script_for" is a hash +of those fields at the time, when they are changed afterwards the script is OUTDATED: it is kept +but not run until it is generated again, edited or approved. "id" identifies a test when it is +renamed. + +A test with a matrix is run once for every combination of the axis values (the cartesian +product). {axis} in steps/expected/todo is replaced by the value; {text} that is not an axis +is left as it is. + + testfile.py convert IN.md OUT.json import a markdown test file +""" + +import hashlib +import itertools +import json +import os +import re +import shutil +import sys + +AXIS_RE = re.compile(r'^\w+$') +PROJECT_RE = re.compile(r'^[\w-]+$') +PROJECTS = 'projects' # test projects: one directory per project, next to the test files +EDITIONS = ('opensource', 'premium') + +SCRIPT_DOC = """A script is a list of steps. Every step is an object with "do" (an action) or "expect" (a check), +and an optional "label" that says what the step is for (shown when it fails). Elements are found +with name (substring of the accessible name/text, case-insensitive), role, path, nth, exact, like +the guitest.py commands. {work} is a temporary directory for files, {home} the HOME of the +application, {project:NAME} the copy of the test project NAME ({project} when the test has just one), +use those for the files of a test instead of write steps. {axis} values of the test matrix and {edition} +(opensource or premium, the same script is used for both) are replaced before the run. + +actions + {"do": "click|rightclick|doubleclick|focus|hover", "name": "...", "role": "...", "mouse": true} + {"do": "settext", "name": "...", "value": "..."} set the text of an edit field + {"do": "type", "value": "..."} keyboard input to the focused element + {"do": "key", "keys": ["ctrl+o", "Return"]} xdotool key names + {"do": "wait", "name": "...", "gone": true, "timeout": 10} + {"do": "restart"} quit and start the application again + {"do": "sleep", "seconds": 1} + {"do": "write", "path": "{work}/a.c", "content": "..."} + {"do": "copy", "from": "{project:p}/a_v2.c", "to": "{project:p}/a.c"} change a file of the project +checks, the test fails when one is wrong + {"expect": "visible", "name": "...", "role": "...", "timeout": 5} + {"expect": "gone", "name": "..."} + {"expect": "state", "name": "...", "state": "checked"} or "not_state": "checked"; also + showing enabled focused selected expanded + {"expect": "value", "name": "...", "equals": "1"} value or text of a field, a spin box... + {"expect": "exited"} the application has ended by itself, status 0 (Quit) + {"expect": "text", "contains": "..."} or "not_contains": anywhere on screen + {"expect": "ocr", "contains": "Recheck"} text on the screen read by OCR (tesseract), or "not_contains"; + only for what "text" cannot see, e.g. a context menu that + is not in the accessibility tree. Case is ignored. + {"expect": "file", "path": "{home}/x.xml", "contains": "...", "not_contains": "...", "regex": "..."} + {"expect": "alive"} + {"expect": "capture", "name": "...", "role": "frame", "regex": "'Cppcheck (\\d[^']*)'", "as": "version"} + the regex is searched in the element's info line + (role 'name' text='...' value=...), group 1 (or the + whole match) is stored and {version} in the later + steps is replaced by it; use it instead of hard coding + text that changes, e.g. the version +A check can have "observed_fail": true when the application was seen not to do what the check says +(a known failure, only for the check that failed). Text checks wait up to "timeout" seconds (default 5). +Actions that need an element wait up to "timeout" seconds (default 5) for it to appear.""" + +SELECTOR = {'name', 'role', 'path', 'nth', 'exact'} +# step name -> (required keys, allowed optional keys) +STEPS = { + 'do': { + 'click': ((), SELECTOR | {'mouse'}), 'rightclick': ((), SELECTOR), 'doubleclick': ((), SELECTOR), + 'focus': ((), SELECTOR), 'hover': ((), SELECTOR), + 'settext': (('value',), SELECTOR), 'type': (('value',), set()), 'key': (('keys',), set()), + 'wait': ((), SELECTOR | {'gone'}), 'restart': ((), set()), + 'sleep': (('seconds',), set()), 'write': (('path', 'content'), set()), 'copy': (('from', 'to'), set()), + }, + 'expect': { + 'visible': ((), SELECTOR), 'gone': ((), SELECTOR), 'value': (('equals',), SELECTOR), 'exited': ((), set()), + 'state': ((), SELECTOR | {'state', 'not_state'}), + 'text': ((), {'contains', 'not_contains'}), 'file': (('path',), {'contains', 'not_contains', 'regex'}), + 'alive': ((), set()), 'ocr': ((), {'contains', 'not_contains'}), + 'capture': (('regex', 'as'), SELECTOR), + }, +} +NEEDS_SELECTOR = {'click', 'rightclick', 'doubleclick', 'focus', 'hover', 'settext', 'wait', 'visible', 'gone', 'state', 'value', + 'capture'} +STATES = {'showing', 'enabled', 'focused', 'selected', 'expanded', 'checked', 'editable', 'modal'} + + +def validate_script(script, where='script'): + if not isinstance(script, list): + return [where + ': must be a list of steps'] + errors = [] + for i, step in enumerate(script, 1): + w = '%s step %d' % (where, i) + if not isinstance(step, dict): + errors.append(w + ': not an object') + continue + kinds = [k for k in ('do', 'expect') if k in step] + if len(kinds) != 1: + errors.append(w + ': needs exactly one of "do" or "expect"') + continue + kind, action = kinds[0], step[kinds[0]] + if action not in STEPS[kind]: + errors.append('%s: unknown %s "%s", use one of %s' % (w, kind, action, ', '.join(STEPS[kind]))) + continue + required, optional = STEPS[kind][action] + allowed = set(required) | optional | {kind, 'label', 'timeout'} | ({'observed_fail'} if kind == 'expect' else set()) + for k in step: + if k not in allowed: + errors.append('%s: "%s" is not used by %s %s' % (w, k, kind, action)) + for k in required: + if k not in step: + errors.append('%s: %s %s needs "%s"' % (w, kind, action, k)) + if action in NEEDS_SELECTOR and not (SELECTOR - {'nth', 'exact'}) & set(step): + errors.append('%s: %s %s needs name, role or path' % (w, kind, action)) + if action in ('text', 'ocr') and not {'contains', 'not_contains'} & set(step): + errors.append(w + ': expect %s needs contains or not_contains' % action) + if action == 'state' and not {'state', 'not_state'} & set(step): + errors.append(w + ': expect state needs state or not_state') + for k in ('state', 'not_state'): + if k in step and step[k] not in STATES: + errors.append('%s: unknown state "%s", use one of %s' % (w, step[k], ', '.join(sorted(STATES)))) + if action == 'capture': + try: + re.compile(step['regex']) + except (re.error, TypeError) as e: + errors.append('%s: bad regex: %s' % (w, e)) + if not re.fullmatch(r'[\w-]+', str(step['as'])): + errors.append(w + ': "as" must be a name of letters, digits, _ and -') + if script and not any('expect' in s for s in script if isinstance(s, dict)): + errors.append(where + ': needs at least one "expect" step, otherwise nothing is tested') + return errors + + +def from_markdown(text): + m = re.search(r'^#\s+(.+)$', text, re.M) + doc = {'title': m.group(1).strip() if m else '', 'tests': []} + for m in re.finditer(r'^## Test:\s*(.+?)\s*\n(.*?)(?=^## |\Z)', text, re.S | re.M): + test = {'name': m.group(1), 'steps': [], 'expected': '', 'todo': ''} + field = None + for line in m.group(2).splitlines(): + s = line.strip() + if s.startswith('Ticket:'): + test['ticket'] = s[len('Ticket:'):].strip() + field = None + elif s.startswith('EXPECTED:'): + test['expected'] = s[len('EXPECTED:'):].strip() + field = 'expected' + elif s.startswith('TODO:'): + test['todo'] = s[len('TODO:'):].strip() + field = 'todo' + elif not s: + field = None + elif field: + test[field] += '\n' + s + else: + test['steps'].append(re.sub(r'^\d+\.\s+', '', s)) + doc['tests'].append(test) + return doc + + +def validate(doc, root=None): + """Return a list of error strings, empty when the document is fine. With ROOT (the directory of + the test file) the test projects must exist.""" + errors = [] + if not isinstance(doc, dict) or not isinstance(doc.get('tests'), list): + return ['the document needs a "tests" list'] + names = set() + for i, t in enumerate(doc['tests'], 1): + where = 'test %d' % i + if not isinstance(t, dict): + errors.append(where + ': not an object') + continue + name = t.get('name') + if not isinstance(name, str) or not name.strip(): + errors.append(where + ': name is missing') + elif name in names: + errors.append('%s: duplicate name "%s"' % (where, name)) + else: + names.add(name) + if not isinstance(t.get('steps', []), list) or not all(isinstance(s, str) for s in t.get('steps', [])): + errors.append(where + ': steps must be a list of strings') + for k in ('expected', 'todo', 'ticket'): + if not isinstance(t.get(k, ''), str): + errors.append('%s: %s must be a string' % (where, k)) + projects = t.get('projects', []) + if not isinstance(projects, list) or not all(isinstance(p, str) and PROJECT_RE.match(p) for p in projects): + errors.append(where + ': projects must be a list of project names (letters, digits, _ and -)') + elif root: + errors += ['%s: the test project "%s" does not exist' % (where, p) for p in projects + if not os.path.isdir(os.path.join(root, PROJECTS, p))] + editions = t.get('editions', list(EDITIONS)) + if (not isinstance(editions, list) or not editions or len(set(editions)) != len(editions) + or not all(e in EDITIONS for e in editions)): + errors.append('%s: editions must be a list of one or more of %s' % (where, ', '.join(EDITIONS))) + errors += validate_script(t.get('script', []), where + ' script') + matrix = t.get('matrix', {}) + if not isinstance(matrix, dict): + errors.append(where + ': matrix must be an object') + continue + for axis, values in matrix.items(): + if not AXIS_RE.match(axis): + errors.append('%s: axis name "%s" must be letters, digits and _' % (where, axis)) + if not isinstance(values, list) or not values or not all(isinstance(v, str) and v for v in values): + errors.append('%s: axis "%s" needs a list of non-empty values' % (where, axis)) + return errors + + +def load(path): + with open(path, encoding='utf-8') as f: + text = f.read() + return json.loads(text) if path.endswith('.json') else from_markdown(text) + + +def combinations(test): + """The matrix of a test as a list of {axis: value}; [{}] when there is no matrix.""" + matrix = test.get('matrix') or {} + axes = list(matrix) + return [dict(zip(axes, values)) for values in itertools.product(*(matrix[a] for a in axes))] + + +def editions(test): + """The editions that a test is for, all of them when it does not say.""" + return test.get('editions') or list(EDITIONS) + + +def substitute(text, combo): + return re.sub(r'\{([\w:-]+)\}', lambda m: combo.get(m.group(1), m.group(0)), text) + + +def body(test, combo, edition=None): + parts = [] + if test.get('ticket'): + parts.append('Ticket: ' + test['ticket']) + if edition: + parts.append('Edition of the application: ' + edition) + if combo: + parts.append('Matrix values for this run: ' + ', '.join('%s=%s' % kv for kv in combo.items())) + combo = dict(combo, edition=edition) if edition else combo + if test.get('steps'): + lines, n = [], 0 + for s in test['steps']: + s = substitute(s, combo) + if s.startswith(('* ', '- ')): # a sub item of the step above + lines.append(' ' + s) + else: + n += 1 + lines.append('%d. %s' % (n, s)) + parts.append('Steps:\n' + '\n'.join(lines)) + parts.append('EXPECTED: ' + substitute(test.get('expected', ''), combo)) + if test.get('todo'): + parts.append('TODO: ' + substitute(test['todo'], combo)) + return '\n\n'.join(parts) + + +def project_files(root, name): + """{relative path: bytes} of a test project.""" + base = os.path.join(root, PROJECTS, name) + out = {} + for dirpath, _, files in os.walk(base): + for f in files: + p = os.path.join(dirpath, f) + with open(p, 'rb') as fh: + out[os.path.relpath(p, base)] = fh.read() + return out + + +def project_hash(root, name): + h = hashlib.sha1() + for path, data in sorted(project_files(root, name).items()): + h.update(path.encode() + b'\0' + data + b'\0') + return h.hexdigest()[:12] + + +def materialize(root, names, work): + """Copy the test projects to WORK/projects/NAME. Returns the variables for the script.""" + variables = {} + for name in names: + src = os.path.join(root, PROJECTS, name) + if not os.path.isdir(src): + raise FileNotFoundError('the test project "%s" does not exist' % name) + dst = os.path.join(work, PROJECTS, name) + shutil.copytree(src, dst) + variables['project:' + name] = dst + if len(names) == 1: + variables['project'] = variables['project:' + names[0]] + return variables + + +def definition_hash(test, root=None): + """What a script depends on: changing any of these, or a file of a test project, makes the script outdated.""" + d = {k: test.get(k) or ([] if k == 'steps' else {} if k == 'matrix' else '') + for k in ('steps', 'expected', 'todo', 'matrix')} + if test.get('projects'): + d['projects'] = {p: project_hash(root, p) if root and os.path.isdir(os.path.join(root, PROJECTS, p)) else 'missing' + for p in test['projects']} + return hashlib.sha1(json.dumps(d, sort_keys=True, ensure_ascii=False).encode()).hexdigest()[:12] + + +def ensure_ids(doc): + for t in doc.get('tests', []): + if not t.get('id'): + t['id'] = 't' + hashlib.sha1(str(t.get('name', '')).encode()).hexdigest()[:8] + return doc + + +def is_stale(test, root=None): + """A script that was made for another version of the test. No script_for: old file, trusted.""" + return bool(test.get('script') and test.get('script_for') and test['script_for'] != definition_hash(test, root)) + + +def stamp_scripts(doc, old, root=None): + """Before saving. A new, edited or approved ("_approve") script is current for the test as it is + now. An untouched script keeps what it was made for, so editing the test makes it outdated.""" + ensure_ids(doc) + prev = {t['id']: t for t in ensure_ids(old or {}).get('tests', [])} + for t in doc['tests']: + t.pop('_stale', None) + approve = t.pop('_approve', False) + if not t.get('script'): + t.pop('script_for', None) + continue + p = prev.get(t['id']) + if approve or p is None or p.get('script') != t['script']: + t['script_for'] = definition_hash(t, root) + else: + t['script_for'] = p.get('script_for') or definition_hash(p, root) + return doc + + +def subst_deep(obj, combo): + if isinstance(obj, str): + return substitute(obj, combo) + if isinstance(obj, list): + return [subst_deep(x, combo) for x in obj] + if isinstance(obj, dict): + return {k: subst_deep(v, combo) for k, v in obj.items()} + return obj + + +def expand(path, edition=None): + """One dict {name, id, body, script, stale, combo} per matrix combination of every enabled test. + With EDITION only the tests for that edition, and {edition} is replaced.""" + doc = ensure_ids(load(path)) + root = os.path.dirname(os.path.abspath(path)) + errors = validate(doc, root) + if errors: + sys.exit('error: %s: %s' % (path, '; '.join(errors))) + out = [] + for t in doc['tests']: + if not t.get('enabled', True) or edition and edition not in editions(t): + continue + for combo in combinations(t): + name = t['name'] + (' [%s]' % ', '.join('%s=%s' % kv for kv in combo.items()) if combo else '') + values = dict(combo, edition=edition) if edition else combo + out.append({'name': name, 'id': t['id'], 'body': body(t, combo, edition), 'combo': combo, 'root': root, + 'projects': t.get('projects', []), 'stale': is_stale(t, root), + 'script': subst_deep(t.get('script', []), values)}) + return out + + +def save(path, doc): + tmp = path + '.tmp' + with open(tmp, 'w', encoding='utf-8') as f: + json.dump(doc, f, indent=2, ensure_ascii=False) + f.write('\n') + os.replace(tmp, path) + + +if __name__ == '__main__': + if len(sys.argv) != 4 or sys.argv[1] != 'convert': + sys.exit(__doc__) + save(sys.argv[3], load(sys.argv[2])) diff --git a/gui/manualtest/tests/analysis.json b/gui/manualtest/tests/analysis.json new file mode 100644 index 00000000000..4277443d5cf --- /dev/null +++ b/gui/manualtest/tests/analysis.json @@ -0,0 +1,457 @@ +{ + "title": "Analysis and recheck", + "tests": [ + { + "id": "te65b1fa8", + "name": "Reanalyze all files", + "steps": [ + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Edit the files {project:basic-project}/src/a.c and {project:basic-project}/src/b.c so they only contain \";\".", + "Click Reanalyze all files and wait until the analysis is done." + ], + "expected": "When the analysis is done there should be no results for a.c and b.c", + "script": [ + { + "label": "Open the File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Choose Open Project File", + "do": "click", + "name": "Open Project File", + "role": "menu item" + }, + { + "label": "Enter the project file path", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck" + }, + { + "label": "Confirm the file dialog", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "Wait until the first analysis shows results for a.c", + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30 + }, + { + "label": "Results for b.c are shown too", + "expect": "visible", + "name": "src/b.c", + "role": "table cell" + }, + { + "label": "Edit a.c so it only contains ;", + "do": "write", + "path": "{project:basic-project}/src/a.c", + "content": ";" + }, + { + "label": "Edit b.c so it only contains ;", + "do": "write", + "path": "{project:basic-project}/src/b.c", + "content": ";" + }, + { + "label": "Click Reanalyze all files", + "do": "click", + "name": "Reanalyze all files", + "role": "button" + }, + { + "label": "Wait until the analysis is done (No errors found dialog)", + "expect": "visible", + "name": "No errors found", + "role": "label", + "timeout": 30 + }, + { + "label": "Close the dialog", + "do": "click", + "name": "OK", + "role": "button" + }, + { + "label": "No results for a.c", + "expect": "gone", + "name": "src/a.c", + "role": "table cell" + }, + { + "label": "No results for b.c", + "expect": "gone", + "name": "src/b.c", + "role": "table cell" + } + ], + "projects": [ + "basic-project" + ], + "script_for": "0198ba992317" + }, + { + "id": "tc707b85b", + "name": "Recheck a changed file", + "ticket": "#14893", + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:recheck}/p.cppcheck and wait until the analysis is done. The results show a nullPointer error in a.c and an arrayIndexOutOfBounds error in b.c.", + "Change a.c: copy {project:recheck}/a_v2.c over {project:recheck}/a.c (a_v2.c has an uninitvar error instead).", + "Right click on the file a.c in the results and click Recheck." + ], + "expected": "The results for a.c are refreshed: the nullPointer error is gone and the uninitvar error from the changed a.c is shown. The results for b.c are still shown, unchanged (arrayIndexOutOfBounds in b.c).", + "script": [ + { + "do": "wait", + "name": "Cppcheck", + "role": "frame", + "timeout": 20, + "label": "Application started with no project" + }, + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the project file dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:recheck}/p.cppcheck", + "label": "Enter the path of p.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project" + }, + { + "expect": "visible", + "name": "b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis done, b.c in the results" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 30, + "label": "a.c in the results" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the analysis finish" + }, + { + "do": "copy", + "from": "{project:recheck}/a_v2.c", + "to": "{project:recheck}/a.c", + "label": "Change a.c by copying a_v2.c over it" + }, + { + "do": "rightclick", + "name": "a.c", + "role": "table cell", + "nth": 0, + "label": "Right click on a.c in the results" + }, + { + "expect": "ocr", + "contains": "Recheck", + "label": "Context menu shows Recheck" + }, + { + "do": "key", + "keys": [ + "Down", + "Return" + ], + "label": "Click Recheck" + }, + { + "do": "sleep", + "seconds": 4, + "label": "Wait for the recheck" + }, + { + "do": "click", + "name": "b.c", + "role": "table cell", + "nth": 0, + "label": "Select the b.c row" + }, + { + "do": "key", + "keys": [ + "Right", + "Down", + "Down", + "Down", + "Right" + ], + "label": "Expand b.c and a.c to show their results" + }, + { + "expect": "text", + "contains": "uninitvar", + "label": "The uninitvar error of the changed a.c is shown" + }, + { + "expect": "text", + "not_contains": "nullPointer", + "label": "The nullPointer error is gone" + }, + { + "expect": "text", + "contains": "arrayIndexOutOfBounds", + "label": "The arrayIndexOutOfBounds error in b.c is still shown" + } + ], + "projects": [ + "recheck" + ], + "script_for": "07cf713caa82" + }, + { + "id": "tc550387d", + "name": "Recheck a header file", + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Right click on the file util.h in the results and click Recheck. Wait until the analysis is done." + ], + "expected": "The context menu of util.h has a Recheck entry. The analysis is run again for the files that include util.h and the nullPointer error in util.h is shown again afterwards.", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and util.h is in the results" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis finished for all files" + }, + { + "do": "rightclick", + "name": "include/util.h", + "role": "table cell", + "label": "Right click on util.h in the results" + }, + { + "do": "sleep", + "seconds": 1, + "label": "Wait for the context menu" + }, + { + "do": "key", + "keys": [ + "Down", + "Return" + ], + "label": "Click the Recheck entry of the context menu" + }, + { + "expect": "text", + "contains": "Checking /tmp", + "timeout": 30, + "label": "The analysis was run again" + }, + { + "expect": "text", + "contains": "src/a.c ...", + "timeout": 30, + "label": "The file that includes util.h is checked again" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Wait until the analysis is done" + }, + { + "do": "click", + "name": "include/util.h", + "role": "table cell", + "mouse": true, + "label": "Select util.h with the mouse so it has focus" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand util.h" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "timeout": 10, + "label": "The nullPointer error in util.h is shown again" + }, + { + "expect": "visible", + "name": "Null pointer dereference", + "role": "table cell", + "timeout": 5, + "label": "The nullPointer message is shown" + } + ], + "projects": [ + "basic-project" + ], + "script_for": "e29e38750a0d" + }, + { + "id": "ta6f3b40b", + "name": "Analyze a broken source file", + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:syntax-error}/{case}.cppcheck and wait until the analysis is done. It checks {case}.c, a broken or unusual source file, and ok.c, which has a nullPointer error." + ], + "expected": "The analysis finishes and the application does not crash. The nullPointer error in ok.c is shown.", + "script": [ + { + "label": "Start the application with no project loaded", + "expect": "visible", + "name": "Cppcheck", + "role": "frame" + }, + { + "label": "Open the project file", + "do": "click", + "name": "Open Project File", + "role": "button" + }, + { + "label": "Wait for the Select Project File dialog", + "do": "wait", + "name": "Select Project File", + "timeout": 10 + }, + { + "label": "Enter the project file {case}.cppcheck", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:syntax-error}/{case}.cppcheck" + }, + { + "label": "Confirm the file dialog", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "The project is loaded", + "expect": "visible", + "name": "Project: {case}.cppcheck", + "role": "frame", + "timeout": 15 + }, + { + "label": "Wait until the analysis is done (ok.c is checked)", + "expect": "text", + "contains": "Checking ok.c", + "timeout": 60 + }, + { + "label": "Give the analysis time to finish", + "do": "sleep", + "seconds": 3 + }, + { + "label": "The analysis finishes: Stop analysis is disabled again", + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 60 + }, + { + "label": "The analysis finishes: Reanalyze all files is enabled again", + "expect": "state", + "name": "Reanalyze all files", + "role": "button", + "state": "enabled", + "timeout": 30 + }, + { + "label": "The broken source file {case}.c was analyzed", + "expect": "text", + "contains": "Checking {case}.c" + }, + { + "label": "The nullPointer error in ok.c is shown (ok.c has a result row)", + "expect": "visible", + "name": "ok.c", + "role": "table cell" + }, + { + "label": "The application does not crash", + "expect": "alive" + } + ], + "projects": [ + "syntax-error" + ], + "matrix": { + "case": [ + "braces", + "empty", + "binary", + "latin1", + "long_line", + "unclosed_nesting" + ] + }, + "script_for": "ed80d3198afb" + } + ] +} diff --git a/gui/manualtest/tests/application.json b/gui/manualtest/tests/application.json new file mode 100644 index 00000000000..863ef4d26c4 --- /dev/null +++ b/gui/manualtest/tests/application.json @@ -0,0 +1,607 @@ +{ + "title": "Application, menus and help", + "tests": [ + { + "id": "t41f9102b", + "name": "All menus open", + "steps": [ + "Start the application with no project loaded.", + "Click the File menu, look at its entries and close it with Escape. Do the same for the Edit, View, Analyze and Help menus." + ], + "expected": "Each of the menus File, Edit, View, Analyze and Help opens and shows at least one entry.", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "expect": "visible", + "name": "Open XML...", + "role": "menu item", + "label": "File menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the File menu" + }, + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "expect": "visible", + "name": "Clear results", + "role": "menu item", + "label": "Edit menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the Edit menu" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "expect": "visible", + "name": "Toolbars", + "role": "menu item", + "label": "View menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the View menu" + }, + { + "do": "click", + "name": "Analyze", + "role": "menu item", + "label": "Open the Analyze menu" + }, + { + "expect": "visible", + "name": "Files...", + "role": "menu item", + "label": "Analyze menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the Analyze menu" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "expect": "visible", + "name": "Contents", + "role": "menu item", + "label": "Help menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the Help menu" + } + ], + "script_for": "647c96907388" + }, + { + "id": "t1cc2be0f", + "name": "Show and hide toolbars", + "steps": [ + "Start the application with no project loaded.", + "Open View > Toolbar. All entries are checked.", + "Uncheck the entries one at a time." + ], + "expected": "A toolbar that is unchecked is no longer shown in the main window. The toolbars that are still checked are shown.", + "script": [ + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open View menu" + }, + { + "do": "click", + "name": "Toolbars", + "role": "menu item", + "label": "Open Toolbars submenu" + }, + { + "expect": "state", + "name": "Standard", + "role": "menu item", + "state": "checked", + "label": "Standard toolbar entry is checked" + }, + { + "expect": "state", + "name": "Categories", + "role": "menu item", + "state": "checked", + "label": "Categories toolbar entry is checked" + }, + { + "expect": "state", + "name": "Filter", + "role": "menu item", + "state": "checked", + "label": "Filter toolbar entry is checked" + }, + { + "do": "click", + "name": "Standard", + "role": "menu item", + "label": "Uncheck Standard toolbar" + }, + { + "expect": "gone", + "name": "Standard", + "role": "tool bar", + "label": "Standard toolbar is no longer shown" + }, + { + "expect": "visible", + "name": "Categories", + "role": "tool bar", + "label": "Categories toolbar still shown" + }, + { + "expect": "visible", + "name": "Filter", + "role": "tool bar", + "label": "Filter toolbar still shown" + }, + { + "do": "click", + "name": "Categories", + "role": "menu item", + "label": "Uncheck Categories toolbar" + }, + { + "expect": "gone", + "name": "Categories", + "role": "tool bar", + "label": "Categories toolbar is no longer shown" + }, + { + "expect": "visible", + "name": "Filter", + "role": "tool bar", + "label": "Filter toolbar still shown" + }, + { + "do": "click", + "name": "Filter", + "role": "menu item", + "label": "Uncheck Filter toolbar" + }, + { + "expect": "gone", + "name": "Filter", + "role": "tool bar", + "label": "Filter toolbar is no longer shown" + } + ], + "script_for": "7672e4ede060" + }, + { + "id": "t5fdeee58", + "name": "Same version in the title and in About", + "steps": [ + "Start the application with no project loaded.", + "Note the version in the title of the main window (Cppcheck ).", + "Open Help > About." + ], + "expected": "The About dialog shows the same version as the title of the main window.", + "script": [ + { + "expect": "capture", + "name": "Cppcheck", + "role": "frame", + "regex": "'Cppcheck (?:Premium )?(\\d[^']*)'", + "as": "version", + "label": "Note the version in the title of the main window" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "About...", + "role": "menu item", + "label": "Open Help > About" + }, + { + "expect": "visible", + "name": "About Cppcheck", + "role": "dialog", + "label": "About dialog is shown" + }, + { + "expect": "visible", + "name": "Version {version}", + "role": "label", + "label": "About dialog shows the same version as the main window title ({version})" + } + ], + "editions": [ + "opensource" + ], + "script_for": "1e35dcba1b6d" + }, + { + "id": "t90321d9c", + "name": "Help contents", + "steps": [ + "Start the application with no project loaded.", + "Open Help > Contents." + ], + "expected": "A help window opens and it has contents (it is not empty).", + "script": [ + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "Contents", + "role": "menu item", + "label": "Choose Help > Contents" + }, + { + "expect": "visible", + "name": "Cppcheck GUI help", + "role": "dialog", + "label": "A help window opens", + "observed_fail": true + }, + { + "expect": "text", + "contains": "Contents", + "label": "The help window has contents (it is not empty)" + }, + { + "expect": "text", + "not_contains": "was not found", + "label": "No error that the help file was not found" + } + ], + "script_for": "eabc2cbcf729" + }, + { + "id": "tc059b730", + "name": "Other Help menu entries", + "steps": [ + "Start the application with no project loaded.", + "Open each of the other entries of the Help menu in turn (About and the rest). Close each window with Escape or its Close button." + ], + "expected": "Every entry opens a dialog or window with text in it. None is empty.", + "script": [ + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "License...", + "role": "menu item", + "label": "Open License" + }, + { + "expect": "visible", + "name": "License", + "role": "dialog", + "label": "License dialog opens" + }, + { + "expect": "text", + "contains": "GNU GENERAL PUBLIC LICENSE", + "label": "License dialog has text" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close License with Escape" + }, + { + "expect": "gone", + "name": "License", + "label": "License dialog is closed" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "Authors...", + "role": "menu item", + "label": "Open Authors" + }, + { + "expect": "visible", + "name": "Authors", + "role": "dialog", + "label": "Authors dialog opens" + }, + { + "expect": "text", + "contains": "The cppcheck team", + "label": "Authors dialog has text" + }, + { + "do": "click", + "name": "Close", + "role": "button", + "label": "Close Authors with its Close button" + }, + { + "expect": "gone", + "name": "Authors", + "role": "dialog", + "label": "Authors dialog is closed" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "About...", + "role": "menu item", + "label": "Open About" + }, + { + "expect": "visible", + "name": "About Cppcheck", + "role": "dialog", + "label": "About dialog opens" + }, + { + "expect": "text", + "contains": "Cppcheck - A tool for static C/C++ code analysis.", + "label": "About dialog has text" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close About with Escape" + }, + { + "expect": "gone", + "name": "About Cppcheck", + "role": "dialog", + "label": "About dialog is closed" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "Contents", + "role": "menu item", + "label": "Open Contents" + }, + { + "expect": "visible", + "name": "Cppcheck", + "role": "alert", + "label": "Contents shows a message" + }, + { + "expect": "text", + "contains": "Helpfile", + "label": "Message has text" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the message with Escape" + }, + { + "expect": "visible", + "name": "Cppcheck GUI help", + "role": "dialog", + "label": "Help window opens" + }, + { + "expect": "text", + "contains": "Introduction", + "observed_fail": true, + "label": "Help window has text in it, not empty" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the help window with Escape" + } + ], + "script_for": "63a2968914df" + }, + { + "id": "t5bf8f6ae", + "name": "Scratch pad", + "ticket": "#13982", + "steps": [ + "Start the application with no project loaded.", + "Open View > Scratch Pad.", + "Enter the code \"int main(void)\", \"{\", \" int *p = 0;\", \" return *p;\", \"}\" (one line each) and click Check." + ], + "expected": "The code is analysed: a nullPointer error is shown and the application does not crash.", + "script": [ + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Show Scratchpad", + "role": "menu item", + "label": "Open View > Scratch Pad" + }, + { + "expect": "visible", + "name": "Scratchpad", + "role": "dialog", + "label": "Scratchpad dialog is shown" + }, + { + "do": "focus", + "path": "1.1", + "label": "Focus the code editor of the scratchpad" + }, + { + "do": "type", + "value": "int main(void)", + "label": "Type line 1" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": "{", + "label": "Type line 2" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": " int *p = 0;", + "label": "Type line 3" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": " return *p;", + "label": "Type line 4" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": "}", + "label": "Type line 5" + }, + { + "do": "click", + "name": "Check", + "role": "button", + "label": "Click Check" + }, + { + "expect": "text", + "contains": "nullPointer", + "timeout": 15, + "label": "A nullPointer error is shown" + }, + { + "expect": "text", + "contains": "Null pointer dereference: p", + "label": "The null pointer message is shown" + }, + { + "expect": "alive", + "label": "The application did not crash" + } + ], + "script_for": "d09d0e350bbd" + }, + { + "id": "t1a2285d8", + "name": "Quit from the File menu", + "steps": [ + "Start the application with no project loaded.", + "Open File > Quit." + ], + "expected": "The application closes.", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Quit", + "role": "menu item", + "label": "Choose File > Quit" + }, + { + "expect": "exited", + "label": "The application closes" + } + ], + "script_for": "dfb4abb73512" + } + ] +} diff --git a/gui/manualtest/tests/preferences.json b/gui/manualtest/tests/preferences.json new file mode 100644 index 00000000000..5eda00d6410 --- /dev/null +++ b/gui/manualtest/tests/preferences.json @@ -0,0 +1,357 @@ +{ + "title": "Preferences", + "tests": [ + { + "id": "t232a9783", + "name": "Default number of threads", + "steps": [ + "Start the application with no project loaded.", + "Open Edit > Preferences and look at the General tab." + ], + "expected": "The number of threads is 1.", + "script": [ + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Edit > Preferences" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown" + }, + { + "expect": "state", + "name": "General", + "role": "page tab", + "state": "selected", + "label": "General tab is shown" + }, + { + "expect": "value", + "name": "Number of threads", + "role": "text", + "equals": "1", + "label": "The number of threads is 1" + } + ], + "script_for": "dbe9c4176eff" + }, + { + "id": "tf5b5a7e1", + "name": "Number of threads cannot be 0", + "steps": [ + "Start the application with no project loaded.", + "Open Edit > Preferences and go to the General tab.", + "Type 0 in the number of threads field and close the dialog with OK.", + "Open Edit > Preferences again." + ], + "expected": "The field does not accept 0: the number of threads is at least 1, it is never 0.", + "script": [ + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Edit > Preferences" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown" + }, + { + "do": "click", + "name": "General", + "role": "page tab", + "label": "Go to the General tab" + }, + { + "do": "settext", + "name": "Number of threads", + "role": "text", + "value": "0", + "label": "Type 0 in the number of threads field" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Close the dialog with OK" + }, + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu again" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Edit > Preferences again" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown again" + }, + { + "do": "click", + "name": "General", + "role": "page tab", + "label": "Go to the General tab again" + }, + { + "expect": "value", + "name": "Number of threads", + "role": "text", + "equals": "1", + "label": "The number of threads is at least 1, never 0" + } + ], + "script_for": "385e7e11379e" + }, + { + "id": "t841cca6c", + "name": "Analysis with 100 threads", + "steps": [ + "Start the application with no project loaded.", + "Open Edit > Preferences, set the number of threads to 100 and close the dialog with OK.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done." + ], + "expected": "The application does not crash: the analysis is done and the results are shown.", + "script": [ + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Preferences from the Edit menu" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown" + }, + { + "do": "settext", + "name": "Number of threads", + "role": "text", + "value": "100", + "label": "Set the number of threads to 100" + }, + { + "expect": "value", + "name": "Number of threads", + "role": "text", + "equals": "100", + "label": "Thread count field holds 100" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Close the Preferences dialog with OK" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "expect": "visible", + "name": "Select Project File", + "role": "dialog", + "label": "File dialog is shown" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 60, + "label": "Analysis is done and results for src/b.c are shown" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "Results for src/a.c are shown" + }, + { + "expect": "text", + "contains": "Project: basic.cppcheck", + "label": "The project is loaded" + }, + { + "expect": "alive", + "label": "The application did not crash" + } + ], + "projects": [ + "basic-project" + ], + "script_for": "8888c70e7498" + }, + { + "id": "t28ce8afe", + "name": "Misra rule text file", + "steps": [ + "Start the application with no project loaded.", + "Open Edit > Preferences and go to the Addons tab.", + "Select the rule text file {project:embedded}/{rulefile} and close the dialog with OK.", + "Open the project file {project:embedded}/embedded.cppcheck and wait until the analysis is done." + ], + "expected": "The Misra error for the unused parameter has the rule text from the file (TEST RULE TEXT: unused parameters are not allowed). There are no internalError results.", + "script": [ + { + "do": "click", + "name": "Preferences", + "role": "button", + "label": "Open Preferences" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "do": "settext", + "name": "MISRA addon", + "role": "text", + "value": "{project:embedded}/{rulefile}", + "label": "Select the rule text file" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Close the dialog with OK" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:embedded}/embedded.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "do": "sleep", + "seconds": 8, + "label": "Wait until the analysis is done" + }, + { + "expect": "visible", + "name": "gpio.c", + "role": "table cell", + "timeout": 20, + "label": "Results for gpio.c are shown" + }, + { + "do": "doubleclick", + "name": "gpio.c", + "role": "table cell", + "label": "Select the gpio.c result row" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand the result row" + }, + { + "expect": "text", + "contains": "TEST RULE TEXT: unused parameters are not allowed", + "timeout": 10, + "label": "Misra error has the rule text from the file" + }, + { + "expect": "text", + "contains": "misra-c2012-2.7", + "label": "The unused parameter Misra rule id is shown" + }, + { + "expect": "text", + "not_contains": "internalError", + "label": "There are no internalError results" + } + ], + "projects": [ + "embedded" + ], + "editions": [ + "opensource" + ], + "matrix": { + "rulefile": [ + "rules.txt", + "rules with spaces.txt" + ] + }, + "script_for": "d3383bf3bd02" + } + ] +} diff --git a/gui/manualtest/tests/project_dialog.json b/gui/manualtest/tests/project_dialog.json new file mode 100644 index 00000000000..e1779d48f9d --- /dev/null +++ b/gui/manualtest/tests/project_dialog.json @@ -0,0 +1,428 @@ +{ + "title": "Project file dialog", + "tests": [ + { + "id": "tcf7ddb76", + "name": "Relative paths in the saved project", + "ticket": "#14983", + "steps": [ + "Start the application with no project loaded.", + "Create a new project file and open the project file dialog.", + "Import the project {project:relative-paths}/compile_commands.json (Paths and defines, Import project).", + "Add the include directory {project:relative-paths}/inc.", + "Exclude the file {project:relative-paths}/skip/b.c.", + "Save the project file as {project:relative-paths}/rel.cppcheck." + ], + "expected": "In the saved rel.cppcheck the import project, the include directory and the excluded file are written relative to the project file (compile_commands.json, inc, skip/b.c). The XML contains no absolute path to the project folder.", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "New Project File", + "role": "menu item", + "label": "Create a new project file" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:relative-paths}/rel.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the project file name" + }, + { + "expect": "visible", + "name": "Project file: rel.cppcheck", + "role": "dialog", + "label": "Project file dialog is open" + }, + { + "do": "click", + "name": "Add...", + "role": "button", + "nth": 1, + "label": "Add an include directory" + }, + { + "do": "settext", + "name": "Directory:", + "role": "text", + "value": "{project:relative-paths}/inc", + "label": "Enter the include directory {project:relative-paths}/inc" + }, + { + "do": "click", + "name": "Choose", + "role": "button", + "label": "Choose the include directory" + }, + { + "do": "click", + "name": "Warning options", + "role": "page tab", + "label": "Open the tab with the excluded files" + }, + { + "do": "click", + "name": "Exclude file", + "role": "button", + "label": "Exclude a file" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:relative-paths}/skip/b.c", + "label": "Enter the excluded file {project:relative-paths}/skip/b.c" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the excluded file" + }, + { + "expect": "visible", + "name": "skip/b.c", + "role": "list item", + "label": "skip/b.c is in the exclude list" + }, + { + "do": "click", + "name": "Paths and Defines", + "role": "page tab", + "label": "Back to Paths and Defines" + }, + { + "do": "click", + "name": "Browse...", + "role": "button", + "nth": 0, + "label": "Browse for the import project" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:relative-paths}/compile_commands.json", + "label": "Enter the import project {project:relative-paths}/compile_commands.json" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the import project" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Save the project file as rel.cppcheck" + }, + { + "do": "sleep", + "seconds": 1 + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Dismiss the build dir question" + }, + { + "expect": "file", + "path": "{project:relative-paths}/rel.cppcheck", + "contains": "compile_commands.json", + "label": "Import project is relative" + }, + { + "expect": "file", + "path": "{project:relative-paths}/rel.cppcheck", + "contains": "", + "label": "Excluded file is relative (skip/b.c)" + }, + { + "expect": "file", + "path": "{project:relative-paths}/rel.cppcheck", + "not_contains": "{project:relative-paths}", + "label": "No absolute path to the project folder in the XML" + } + ], + "projects": [ + "relative-paths" + ], + "script_for": "23c19e2db208" + }, + { + "id": "t5afff002", + "name": "Select the platform pic8.xml", + "ticket": "#14489", + "steps": [ + "Start the application with no project loaded.", + "Create a new project file and open the project file dialog.", + "Open the Platform drop-down list.", + "Select pic8.xml in the list." + ], + "expected": "pic8.xml is in the Platform list, it can be selected and it is then the platform that the dialog shows.", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "New Project File", + "role": "menu item", + "label": "Create a new project file" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{work}/t.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "click", + "name": "Save", + "role": "button", + "exact": true, + "label": "Save the new project file; the project file dialog opens" + }, + { + "expect": "visible", + "name": "Project file", + "role": "dialog", + "label": "Project file dialog is open" + }, + { + "do": "click", + "name": "Types and Functions", + "role": "page tab", + "label": "Go to the tab with the Platform setting" + }, + { + "do": "click", + "name": "Platform", + "role": "combo box", + "label": "Open the Platform drop-down list" + }, + { + "expect": "visible", + "name": "pic8.xml", + "role": "list item", + "label": "pic8.xml is in the Platform list" + }, + { + "do": "click", + "name": "pic8.xml", + "role": "list item", + "exact": true, + "mouse": true, + "label": "Select pic8.xml in the list" + }, + { + "expect": "visible", + "name": "pic8.xml", + "role": "combo box", + "label": "The dialog shows pic8.xml as the platform" + } + ], + "script_for": "83263650eda1" + }, + { + "id": "t91490557", + "name": "Custom cfg file in the library list", + "ticket": "#14672", + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:custom-cfg}/p.cppcheck. Its folder also holds aa.cfg, a copy of cfg/avr.cfg.", + "Open the project file dialog (edit the project file).", + "Look at the library list (Libraries)." + ], + "expected": "aa is in the library list in alphabetical order (before avr).", + "script": [ + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the project file dialog from the toolbar" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:custom-cfg}/p.cppcheck", + "label": "Enter the path of p.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "Cppcheck", + "role": "alert", + "label": "Analysis finishes and an alert appears" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Dismiss the alert" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Edit Project File", + "role": "menu item", + "label": "Open the project file dialog (edit the project file)" + }, + { + "expect": "visible", + "name": "Project file: p.cppcheck", + "role": "dialog", + "label": "The project file dialog is open" + }, + { + "expect": "visible", + "name": "aa", + "role": "list item", + "exact": true, + "label": "aa is in the Libraries list" + }, + { + "expect": "visible", + "name": "avr", + "role": "list item", + "exact": true, + "label": "avr is in the Libraries list" + } + ], + "projects": [ + "custom-cfg" + ], + "script_for": "db079765d5b6" + }, + { + "id": "t0ec0680b", + "name": "Misra checkbox of a loaded project", + "ticket": "#14488", + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:embedded}/embedded.cppcheck.", + "Open the project file dialog (edit the project file).", + "Go to the Addons tab." + ], + "expected": "The Misra C 2012 checkbox is checked.", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "File dialog is shown" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:embedded}/embedded.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "do": "wait", + "name": "Project: embedded.cppcheck", + "role": "frame", + "label": "Project is loaded" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu again" + }, + { + "do": "click", + "name": "Edit Project File...", + "role": "menu item", + "label": "Open the project file dialog" + }, + { + "do": "wait", + "name": "Project file: embedded.cppcheck", + "role": "dialog", + "label": "Project file dialog is shown" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "expect": "state", + "name": "Misra C 2012", + "role": "check box", + "state": "checked", + "label": "The Misra C 2012 checkbox is checked" + } + ], + "projects": [ + "embedded" + ], + "editions": [ + "opensource" + ], + "script_for": "13561e8de354" + } + ] +} diff --git a/gui/manualtest/tests/projects.json b/gui/manualtest/tests/projects.json new file mode 100644 index 00000000000..3fab3af0778 --- /dev/null +++ b/gui/manualtest/tests/projects.json @@ -0,0 +1,855 @@ +{ + "title": "Open and create projects", + "tests": [ + { + "name": "Open a project file", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck with the open project file entry in the File menu." + ], + "expected": "The project opens: the title of the main window names basic.cppcheck and the analysis results are shown (nullPointer errors in a.c and util.h, arrayIndexOutOfBounds in b.c).", + "id": "tf9fc8bf6", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File in the File menu" + }, + { + "expect": "visible", + "name": "Select Project File", + "role": "dialog", + "label": "The project file dialog is shown" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the dialog to open the project" + }, + { + "expect": "visible", + "name": "Project: basic.cppcheck", + "role": "frame", + "timeout": 15, + "label": "The title of the main window names basic.cppcheck" + }, + { + "do": "wait", + "name": "Select Project File", + "gone": true, + "label": "The dialog is closed" + }, + { + "do": "sleep", + "seconds": 4, + "label": "Wait for the analysis to finish" + }, + { + "do": "focus", + "role": "tree", + "label": "Focus the results tree" + }, + { + "do": "key", + "keys": [ + "Home", + "Down", + "Down", + "Right", + "Up", + "Right", + "Up", + "Right", + "Home" + ], + "label": "Expand the results of all three files and go to the top" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "timeout": 10, + "label": "nullPointer errors are shown for util.h and a.c" + }, + { + "expect": "text", + "contains": "src/a.c", + "label": "Results for a.c are shown" + }, + { + "expect": "text", + "contains": "include/util.h", + "label": "Results for util.h are shown" + }, + { + "do": "key", + "keys": [ + "End" + ], + "label": "Scroll to the results of b.c" + }, + { + "expect": "visible", + "name": "arrayIndexOutOfBounds", + "role": "table cell", + "timeout": 10, + "label": "arrayIndexOutOfBounds error is shown for b.c" + } + ], + "script_for": "be86887ae48f" + }, + { + "name": "Open a second project", + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:switch-project}/one/p1.cppcheck and wait until the analysis is done. The results show a nullPointer error in a.c.", + "Open the project file {project:switch-project}/two/p2.cppcheck and wait until the analysis is done." + ], + "expected": "The results of the first project (nullPointer in a.c) are gone and the results of the second project (arrayIndexOutOfBounds in b.c) are shown.", + "todo": "with and without build dir", + "projects": [ + "switch-project" + ], + "id": "t6bb11424", + "script": [ + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the first project" + }, + { + "do": "type", + "value": "{project:switch-project}/one/p1.cppcheck", + "label": "Enter path of p1.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "Project: p1.cppcheck", + "role": "frame", + "timeout": 10, + "label": "First project is loaded" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 20, + "label": "Results of the first project show a.c" + }, + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the second project" + }, + { + "do": "type", + "value": "{project:switch-project}/two/p2.cppcheck", + "label": "Enter path of p2.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "Project: p2.cppcheck", + "role": "frame", + "timeout": 10, + "label": "Second project is loaded" + }, + { + "expect": "visible", + "name": "b.c", + "role": "table cell", + "timeout": 20, + "label": "Results of the second project show b.c" + }, + { + "expect": "gone", + "name": "a.c", + "role": "table cell", + "label": "Results of the first project are gone" + } + ], + "script_for": "2fe0df39eb8b" + }, + { + "name": "Open a recent project", + "projects": [ + "switch-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:switch-project}/one/p1.cppcheck and wait until the analysis is done.", + "Open the project file {project:switch-project}/two/p2.cppcheck and wait until the analysis is done.", + "Open the File menu and click p1.cppcheck in the list of recent projects." + ], + "expected": "The project p1.cppcheck is opened: the title of the main window names p1.cppcheck, not p2.cppcheck or another file.", + "id": "t176fd17f", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:switch-project}/one/p1.cppcheck", + "label": "Enter the path of p1.cppcheck" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Open p1.cppcheck" + }, + { + "expect": "text", + "contains": "Project: p1.cppcheck", + "timeout": 10, + "label": "Project p1 is open" + }, + { + "expect": "visible", + "name": "Checking a.c", + "timeout": 15, + "label": "Analysis of p1 is done" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:switch-project}/two/p2.cppcheck", + "label": "Enter the path of p2.cppcheck" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Open p2.cppcheck" + }, + { + "expect": "text", + "contains": "Project: p2.cppcheck", + "timeout": 10, + "label": "Project p2 is open" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Wait until the analysis of p2 is done" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "p1.cppcheck", + "role": "menu item", + "label": "Click p1.cppcheck in the recent projects list" + }, + { + "expect": "text", + "contains": "Project: p1.cppcheck", + "timeout": 10, + "label": "Main window title names p1.cppcheck" + }, + { + "expect": "text", + "not_contains": "Project: p2.cppcheck", + "label": "Title does not name p2.cppcheck" + } + ], + "script_for": "1e971e851ea7" + }, + { + "name": "New project file", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open File > New Project File.", + "Enter the file name {project:basic-project}/OSgui.cppcheck.", + "In the project file dialog add the folder {project:basic-project}/src as a path to check.", + "Save the project file without selecting an addon and wait until the analysis is done." + ], + "expected": "The project file OSgui.cppcheck is saved without any addon in it and the analysis is run: the results (nullPointer errors in a.c, arrayIndexOutOfBounds in b.c) are shown. util.h is not checked, the new project has no include path to it.", + "id": "t1e899c1c", + "script": [ + { + "label": "Open File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Open File > New Project File", + "do": "click", + "name": "New Project File", + "role": "menu item" + }, + { + "label": "Wait for the file name dialog", + "do": "wait", + "name": "Select Project Filename", + "role": "dialog" + }, + { + "label": "Enter the file name OSgui.cppcheck", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/OSgui.cppcheck" + }, + { + "label": "Confirm the file name", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "Project file dialog is shown", + "expect": "visible", + "name": "Project file: OSgui.cppcheck", + "role": "dialog" + }, + { + "label": "Add a path to check", + "do": "click", + "name": "Add...", + "role": "button", + "nth": 0 + }, + { + "label": "Wait for the directory dialog", + "do": "wait", + "name": "Select a directory to check", + "role": "dialog" + }, + { + "label": "Enter the src folder", + "do": "settext", + "name": "Directory:", + "role": "text", + "value": "{project:basic-project}/src" + }, + { + "label": "Choose the src folder", + "do": "click", + "name": "Choose", + "role": "button" + }, + { + "label": "src is in the paths list", + "expect": "visible", + "name": "src", + "role": "list item" + }, + { + "label": "Save the project file without selecting an addon", + "do": "click", + "name": "OK", + "role": "button" + }, + { + "label": "Create the build dir when asked", + "do": "click", + "name": "Yes", + "role": "button" + }, + { + "label": "Wait until the analysis has checked b.c", + "expect": "text", + "contains": "Checking src/b.c", + "timeout": 30 + }, + { + "label": "Wait for the results of a.c", + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30 + }, + { + "label": "Select a.c results", + "do": "click", + "name": "src/a.c", + "role": "table cell", + "nth": 0, + "mouse": true + }, + { + "label": "Collapse a.c results to get a known state", + "do": "key", + "keys": [ + "Left" + ] + }, + { + "label": "Expand a.c results", + "do": "key", + "keys": [ + "Right" + ] + }, + { + "label": "nullPointer is reported in a.c", + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "timeout": 10 + }, + { + "label": "Select b.c results", + "do": "click", + "name": "src/b.c", + "role": "table cell", + "nth": 0, + "mouse": true + }, + { + "label": "Collapse b.c results to get a known state", + "do": "key", + "keys": [ + "Left" + ] + }, + { + "label": "Expand b.c results", + "do": "key", + "keys": [ + "Right" + ] + }, + { + "label": "arrayIndexOutOfBounds is reported in b.c", + "expect": "visible", + "name": "arrayIndexOutOfBounds", + "role": "table cell", + "timeout": 10 + }, + { + "label": "util.h is not checked", + "expect": "text", + "not_contains": "Checking include/util.h" + }, + { + "label": "Project file contains the src path", + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "contains": "" + }, + { + "label": "Project file has no addon", + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "not_contains": "addon" + }, + { + "label": "Project file has no include path", + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "not_contains": "include" + } + ], + "script_for": "1faee2696e8e" + }, + { + "name": "New project file with an addon", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open File > New Project File.", + "Enter the file name {project:basic-project}/OSgui.cppcheck.", + "In the project file dialog add the folder {project:basic-project}/src as a path to check.", + "On the Addons tab tick the checkbox of the addon whose name contains \"{addon}\" (Misra C, Y2038 or Thread safety).", + "Save the project file and wait until the analysis is done." + ], + "expected": "The project file OSgui.cppcheck is saved with an addon entry that contains \"{addon}\" (misra, y2038 or threadsafety) and the analysis is run: results are shown and none of them is an internalError about the addon.", + "id": "t202b9515", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "New Project File...", + "role": "menu item", + "label": "Open File > New Project File" + }, + { + "do": "wait", + "name": "Select Project Filename", + "role": "dialog", + "label": "Wait for the file name dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/OSgui.cppcheck", + "label": "Enter the file name OSgui.cppcheck" + }, + { + "do": "focus", + "name": "File name:", + "role": "text", + "label": "Focus the file name field" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file name" + }, + { + "do": "wait", + "name": "Project file: OSgui.cppcheck", + "role": "dialog", + "timeout": 10, + "label": "Wait for the project file dialog" + }, + { + "do": "click", + "name": "Paths and Defines", + "role": "page tab", + "label": "Go to the Paths and Defines tab" + }, + { + "do": "click", + "name": "Add...", + "role": "button", + "nth": 0, + "label": "Add a path to check" + }, + { + "do": "wait", + "name": "Select a directory to check", + "role": "dialog", + "label": "Wait for the directory dialog" + }, + { + "do": "settext", + "name": "Directory:", + "role": "text", + "value": "{project:basic-project}/src", + "label": "Enter the folder src" + }, + { + "do": "click", + "name": "Choose", + "role": "button", + "label": "Choose the folder src" + }, + { + "do": "wait", + "name": "Select a directory to check", + "role": "dialog", + "gone": true, + "label": "Directory dialog closes" + }, + { + "expect": "visible", + "name": "src", + "role": "list item", + "exact": true, + "label": "src is in the paths to check" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "do": "click", + "name": "{addon}", + "role": "check box", + "nth": 0, + "label": "Tick the addon checkbox" + }, + { + "expect": "state", + "name": "{addon}", + "role": "check box", + "nth": 0, + "state": "checked", + "label": "The addon checkbox is ticked" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Save the project file" + }, + { + "do": "wait", + "name": "Yes", + "role": "button", + "timeout": 10, + "label": "Question to create the build dir" + }, + { + "do": "click", + "name": "Yes", + "role": "button", + "label": "Create the build dir" + }, + { + "do": "wait", + "name": "src/b.c", + "role": "table cell", + "timeout": 120, + "label": "Wait for the analysis results" + }, + { + "do": "sleep", + "seconds": 3 + }, + { + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 120, + "label": "The analysis is done" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "Select the results of a.c" + }, + { + "do": "key", + "keys": [ + "asterisk", + "End", + "asterisk" + ], + "label": "Expand the results of all files" + }, + { + "do": "sleep", + "seconds": 1 + }, + { + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "contains": "{addon}", + "label": "Project file has the addon entry" + }, + { + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "contains": "", + "label": "Project file has the path src" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "Analysis results are shown" + }, + { + "expect": "text", + "not_contains": "internalError", + "label": "No internalError about the addon in the results" + } + ], + "script_for": "716caa8a0f55", + "matrix": { + "addon": [ + "misra", + "y2038", + "thread" + ] + } + }, + { + "name": "Add the Misra addon to a project", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Open File > Edit Project File.", + "On the Addons tab select MISRA C 2012 and save the project file.", + "Wait until the analysis is done." + ], + "expected": "basic.cppcheck now has the addon misra in it and the analysis is run again.", + "id": "tf6da2cfd", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "Checking src/b.c", + "timeout": 20, + "label": "Wait for the first analysis to run" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the first analysis finish" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Edit Project File", + "role": "menu item", + "label": "Choose Edit Project File" + }, + { + "expect": "visible", + "name": "Project file: basic.cppcheck", + "role": "dialog", + "label": "Project file dialog is open" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "do": "click", + "name": "Misra C 2012", + "role": "check box", + "label": "Select MISRA C 2012" + }, + { + "expect": "state", + "name": "Misra C 2012", + "state": "checked", + "label": "MISRA C 2012 is checked" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Save the project file" + }, + { + "expect": "gone", + "name": "Project file: basic.cppcheck", + "label": "Project file dialog is closed" + }, + { + "do": "sleep", + "seconds": 4, + "label": "Wait for the second analysis to finish" + }, + { + "expect": "file", + "path": "{project:basic-project}/basic.cppcheck", + "contains": "misra", + "label": "Project file has the misra addon" + }, + { + "expect": "visible", + "name": "Checking src/b.c", + "role": "list item", + "nth": 1, + "label": "The analysis was run again" + } + ], + "script_for": "351823845d96" + } + ] +} diff --git a/gui/manualtest/tests/projects/basic-project/basic.cppcheck b/gui/manualtest/tests/projects/basic-project/basic.cppcheck new file mode 100644 index 00000000000..c8805a32587 --- /dev/null +++ b/gui/manualtest/tests/projects/basic-project/basic.cppcheck @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/gui/manualtest/tests/projects/basic-project/include/util.h b/gui/manualtest/tests/projects/basic-project/include/util.h new file mode 100644 index 00000000000..48ae2d54a95 --- /dev/null +++ b/gui/manualtest/tests/projects/basic-project/include/util.h @@ -0,0 +1,5 @@ +static inline int helper(void) +{ + int *r = 0; + return *r; +} diff --git a/gui/manualtest/tests/projects/basic-project/src/a.c b/gui/manualtest/tests/projects/basic-project/src/a.c new file mode 100644 index 00000000000..3a84d86a5ff --- /dev/null +++ b/gui/manualtest/tests/projects/basic-project/src/a.c @@ -0,0 +1,18 @@ +#include "util.h" + +int first(void) +{ + int *p = 0; + return *p; +} + +int second(void) +{ + int *q = 0; + return *q; +} + +int main(void) +{ + return first() + second() + helper(); +} diff --git a/gui/manualtest/tests/projects/basic-project/src/b.c b/gui/manualtest/tests/projects/basic-project/src/b.c new file mode 100644 index 00000000000..20be2edc145 --- /dev/null +++ b/gui/manualtest/tests/projects/basic-project/src/b.c @@ -0,0 +1,5 @@ +void f(void) +{ + char a[4]; + a[10] = 0; +} diff --git a/gui/manualtest/tests/projects/custom-cfg/a.c b/gui/manualtest/tests/projects/custom-cfg/a.c new file mode 100644 index 00000000000..58fe69254d0 --- /dev/null +++ b/gui/manualtest/tests/projects/custom-cfg/a.c @@ -0,0 +1,4 @@ +int main(void) +{ + return 0; +} diff --git a/gui/manualtest/tests/projects/custom-cfg/aa.cfg b/gui/manualtest/tests/projects/custom-cfg/aa.cfg new file mode 100644 index 00000000000..bb21afac352 --- /dev/null +++ b/gui/manualtest/tests/projects/custom-cfg/aa.cfg @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + false + + + 0:255 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + false + + + + + + 0: + + + + false + + + + + + + + + + + + + + false + + + + + + + + + + + + + + false + + + + + + + + false + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + false + + + + + + + false + + + + + + + false + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + false + + + + + + + false + + + + + + + false + + + + + + + false + + + + + + + + + + + + + + + diff --git a/gui/manualtest/tests/projects/custom-cfg/p.cppcheck b/gui/manualtest/tests/projects/custom-cfg/p.cppcheck new file mode 100644 index 00000000000..e8db10582ad --- /dev/null +++ b/gui/manualtest/tests/projects/custom-cfg/p.cppcheck @@ -0,0 +1,6 @@ + + + + + + diff --git a/gui/manualtest/tests/projects/embedded/board.h b/gui/manualtest/tests/projects/embedded/board.h new file mode 100644 index 00000000000..8a5bddc04a0 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/board.h @@ -0,0 +1,10 @@ +#ifndef BOARD_H +#define BOARD_H + +#define GPIOA_ODR (*(volatile unsigned int *)0x40020014U) +#define UART_DR (*(volatile unsigned int *)0x40011004U) + +void gpio_set(unsigned int pin, unsigned int mask); +void uart_putc(char c); + +#endif diff --git a/gui/manualtest/tests/projects/embedded/embedded.cppcheck b/gui/manualtest/tests/projects/embedded/embedded.cppcheck new file mode 100644 index 00000000000..679b87f6d90 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/embedded.cppcheck @@ -0,0 +1,9 @@ + + + + + + + misra + + diff --git a/gui/manualtest/tests/projects/embedded/gpio.c b/gui/manualtest/tests/projects/embedded/gpio.c new file mode 100644 index 00000000000..73968d86135 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/gpio.c @@ -0,0 +1,6 @@ +#include "board.h" + +void gpio_set(unsigned int pin, unsigned int mask) +{ + GPIOA_ODR |= mask; +} diff --git a/gui/manualtest/tests/projects/embedded/main.c b/gui/manualtest/tests/projects/embedded/main.c new file mode 100644 index 00000000000..6daafe949b4 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/main.c @@ -0,0 +1,10 @@ +#include "board.h" + +int main(void) +{ + gpio_set(5U, 0x20U); + uart_putc('A'); + for (;;) + { + } +} diff --git a/gui/manualtest/tests/projects/embedded/rules with spaces.txt b/gui/manualtest/tests/projects/embedded/rules with spaces.txt new file mode 100644 index 00000000000..a707cb8673d --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/rules with spaces.txt @@ -0,0 +1,7 @@ +Appendix A Summary of guidelines + +Rule 2.7 +TEST RULE TEXT: unused parameters are not allowed +Category Advisory +Analysis Decidable, Single Translation Unit +Applies to C90, C99 diff --git a/gui/manualtest/tests/projects/embedded/rules.txt b/gui/manualtest/tests/projects/embedded/rules.txt new file mode 100644 index 00000000000..a707cb8673d --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/rules.txt @@ -0,0 +1,7 @@ +Appendix A Summary of guidelines + +Rule 2.7 +TEST RULE TEXT: unused parameters are not allowed +Category Advisory +Analysis Decidable, Single Translation Unit +Applies to C90, C99 diff --git a/gui/manualtest/tests/projects/embedded/uart.c b/gui/manualtest/tests/projects/embedded/uart.c new file mode 100644 index 00000000000..3720b2fde22 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/uart.c @@ -0,0 +1,6 @@ +#include "board.h" + +void uart_putc(char c) +{ + UART_DR = (unsigned int)c; +} diff --git a/gui/manualtest/tests/projects/recheck/a.c b/gui/manualtest/tests/projects/recheck/a.c new file mode 100644 index 00000000000..a4f00f729cb --- /dev/null +++ b/gui/manualtest/tests/projects/recheck/a.c @@ -0,0 +1,5 @@ +int main(void) +{ + int *p = 0; + return *p; +} diff --git a/gui/manualtest/tests/projects/recheck/a_v2.c b/gui/manualtest/tests/projects/recheck/a_v2.c new file mode 100644 index 00000000000..229abfebb92 --- /dev/null +++ b/gui/manualtest/tests/projects/recheck/a_v2.c @@ -0,0 +1,5 @@ +int main(void) +{ + int x; + return x; +} diff --git a/gui/manualtest/tests/projects/recheck/b.c b/gui/manualtest/tests/projects/recheck/b.c new file mode 100644 index 00000000000..20be2edc145 --- /dev/null +++ b/gui/manualtest/tests/projects/recheck/b.c @@ -0,0 +1,5 @@ +void f(void) +{ + char a[4]; + a[10] = 0; +} diff --git a/gui/manualtest/tests/projects/recheck/p.cppcheck b/gui/manualtest/tests/projects/recheck/p.cppcheck new file mode 100644 index 00000000000..760eb8837f8 --- /dev/null +++ b/gui/manualtest/tests/projects/recheck/p.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/relative-paths/compile_commands.json b/gui/manualtest/tests/projects/relative-paths/compile_commands.json new file mode 100644 index 00000000000..130de6bca98 --- /dev/null +++ b/gui/manualtest/tests/projects/relative-paths/compile_commands.json @@ -0,0 +1,4 @@ +[ + {"directory": ".", "command": "gcc -c src/a.c", "file": "src/a.c"}, + {"directory": ".", "command": "gcc -c skip/b.c", "file": "skip/b.c"} +] diff --git a/gui/manualtest/tests/projects/relative-paths/inc/util.h b/gui/manualtest/tests/projects/relative-paths/inc/util.h new file mode 100644 index 00000000000..874a45c458c --- /dev/null +++ b/gui/manualtest/tests/projects/relative-paths/inc/util.h @@ -0,0 +1 @@ +int helper(int x); diff --git a/gui/manualtest/tests/projects/relative-paths/skip/b.c b/gui/manualtest/tests/projects/relative-paths/skip/b.c new file mode 100644 index 00000000000..5bb061143c0 --- /dev/null +++ b/gui/manualtest/tests/projects/relative-paths/skip/b.c @@ -0,0 +1,4 @@ +int unused(void) +{ + return 0; +} diff --git a/gui/manualtest/tests/projects/relative-paths/src/a.c b/gui/manualtest/tests/projects/relative-paths/src/a.c new file mode 100644 index 00000000000..db8654dea9f --- /dev/null +++ b/gui/manualtest/tests/projects/relative-paths/src/a.c @@ -0,0 +1,6 @@ +#include "util.h" + +int main(void) +{ + return helper(1); +} diff --git a/gui/manualtest/tests/projects/switch-project/one/a.c b/gui/manualtest/tests/projects/switch-project/one/a.c new file mode 100644 index 00000000000..a4f00f729cb --- /dev/null +++ b/gui/manualtest/tests/projects/switch-project/one/a.c @@ -0,0 +1,5 @@ +int main(void) +{ + int *p = 0; + return *p; +} diff --git a/gui/manualtest/tests/projects/switch-project/one/p1.cppcheck b/gui/manualtest/tests/projects/switch-project/one/p1.cppcheck new file mode 100644 index 00000000000..5eb66587401 --- /dev/null +++ b/gui/manualtest/tests/projects/switch-project/one/p1.cppcheck @@ -0,0 +1,6 @@ + + + + + + diff --git a/gui/manualtest/tests/projects/switch-project/two/b.c b/gui/manualtest/tests/projects/switch-project/two/b.c new file mode 100644 index 00000000000..20be2edc145 --- /dev/null +++ b/gui/manualtest/tests/projects/switch-project/two/b.c @@ -0,0 +1,5 @@ +void f(void) +{ + char a[4]; + a[10] = 0; +} diff --git a/gui/manualtest/tests/projects/switch-project/two/p2.cppcheck b/gui/manualtest/tests/projects/switch-project/two/p2.cppcheck new file mode 100644 index 00000000000..a79d287d385 --- /dev/null +++ b/gui/manualtest/tests/projects/switch-project/two/p2.cppcheck @@ -0,0 +1,6 @@ + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/binary.c b/gui/manualtest/tests/projects/syntax-error/binary.c new file mode 100644 index 0000000000000000000000000000000000000000..c91312c3af04c4ea13103c233b48b7de9d4ac210 GIT binary patch literal 4096 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 literal 0 HcmV?d00001 diff --git a/gui/manualtest/tests/projects/syntax-error/binary.cppcheck b/gui/manualtest/tests/projects/syntax-error/binary.cppcheck new file mode 100644 index 00000000000..7804897af2d --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/binary.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/braces.c b/gui/manualtest/tests/projects/syntax-error/braces.c new file mode 100644 index 00000000000..66d81f572c3 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/braces.c @@ -0,0 +1,3 @@ +int main( { + return 0 +} diff --git a/gui/manualtest/tests/projects/syntax-error/braces.cppcheck b/gui/manualtest/tests/projects/syntax-error/braces.cppcheck new file mode 100644 index 00000000000..94d206fd67c --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/braces.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/empty.c b/gui/manualtest/tests/projects/syntax-error/empty.c new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gui/manualtest/tests/projects/syntax-error/empty.cppcheck b/gui/manualtest/tests/projects/syntax-error/empty.cppcheck new file mode 100644 index 00000000000..eb36efbe086 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/empty.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/latin1.c b/gui/manualtest/tests/projects/syntax-error/latin1.c new file mode 100644 index 00000000000..7e730fd3a83 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/latin1.c @@ -0,0 +1,6 @@ +/* färg och smör */ +int färg(void) +{ + const char *s = "grön"; + return s[0]; +} diff --git a/gui/manualtest/tests/projects/syntax-error/latin1.cppcheck b/gui/manualtest/tests/projects/syntax-error/latin1.cppcheck new file mode 100644 index 00000000000..06ccf58dfd5 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/latin1.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/long_line.c b/gui/manualtest/tests/projects/syntax-error/long_line.c new file mode 100644 index 00000000000..14807c9e52b --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/long_line.c @@ -0,0 +1 @@ +int x = a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + diff --git a/gui/manualtest/tests/projects/syntax-error/long_line.cppcheck b/gui/manualtest/tests/projects/syntax-error/long_line.cppcheck new file mode 100644 index 00000000000..f13ad9aa112 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/long_line.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/ok.c b/gui/manualtest/tests/projects/syntax-error/ok.c new file mode 100644 index 00000000000..8e1e58ce827 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/ok.c @@ -0,0 +1,5 @@ +int ok(void) +{ + int *p = 0; + return *p; +} diff --git a/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.c b/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.c new file mode 100644 index 00000000000..7d6ac74625a --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.c @@ -0,0 +1,4 @@ +int f(int a) +{ + return ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((a; +} diff --git a/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.cppcheck b/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.cppcheck new file mode 100644 index 00000000000..baeddf3ef2f --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/xml-results/a.c b/gui/manualtest/tests/projects/xml-results/a.c new file mode 100644 index 00000000000..38775adee5f --- /dev/null +++ b/gui/manualtest/tests/projects/xml-results/a.c @@ -0,0 +1,5 @@ +int first(void) +{ + int *p = 0; + return *p; +} diff --git a/gui/manualtest/tests/projects/xml-results/results.xml b/gui/manualtest/tests/projects/xml-results/results.xml new file mode 100644 index 00000000000..c6f4b14c0c1 --- /dev/null +++ b/gui/manualtest/tests/projects/xml-results/results.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/gui/manualtest/tests/result_list.json b/gui/manualtest/tests/result_list.json new file mode 100644 index 00000000000..4bdebe49408 --- /dev/null +++ b/gui/manualtest/tests/result_list.json @@ -0,0 +1,1198 @@ +{ + "title": "Results list", + "tests": [ + { + "name": "Severity buttons toggle", + "steps": [ + "Start the application with no project loaded.", + "Click each of the toolbar buttons Error, Warning, Style, Portability, Performance, Information, Cppcheck and Clang once.", + "Click each of them once more." + ], + "expected": "Every button can be clicked and changes between checked and unchecked. After the second click all buttons are in the state they had at the start.", + "id": "t852206f1", + "script": [ + { + "do": "click", + "name": "Show errors", + "role": "check box", + "label": "Click Error once" + }, + { + "do": "click", + "name": "Show warnings", + "role": "check box", + "label": "Click Warning once" + }, + { + "do": "click", + "name": "Show style warnings", + "role": "check box", + "label": "Click Style once" + }, + { + "do": "click", + "name": "Show portability", + "role": "check box", + "label": "Click Portability once" + }, + { + "do": "click", + "name": "Show performance warnings", + "role": "check box", + "label": "Click Performance once" + }, + { + "do": "click", + "name": "Show information", + "role": "check box", + "label": "Click Information once" + }, + { + "do": "click", + "name": "Cppcheck", + "role": "check box", + "label": "Click Cppcheck once" + }, + { + "do": "click", + "name": "Clang", + "role": "check box", + "label": "Click Clang once" + }, + { + "expect": "state", + "name": "Show errors", + "role": "check box", + "not_state": "checked", + "label": "Error changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show warnings", + "role": "check box", + "not_state": "checked", + "label": "Warning changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show style warnings", + "role": "check box", + "not_state": "checked", + "label": "Style changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show portability", + "role": "check box", + "not_state": "checked", + "label": "Portability changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show performance warnings", + "role": "check box", + "not_state": "checked", + "label": "Performance changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show information", + "role": "check box", + "not_state": "checked", + "label": "Information changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Cppcheck", + "role": "check box", + "not_state": "checked", + "label": "Cppcheck changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Clang", + "role": "check box", + "not_state": "checked", + "label": "Clang changed to unchecked after first click" + }, + { + "do": "click", + "name": "Show errors", + "role": "check box", + "label": "Click Error a second time" + }, + { + "do": "click", + "name": "Show warnings", + "role": "check box", + "label": "Click Warning a second time" + }, + { + "do": "click", + "name": "Show style warnings", + "role": "check box", + "label": "Click Style a second time" + }, + { + "do": "click", + "name": "Show portability", + "role": "check box", + "label": "Click Portability a second time" + }, + { + "do": "click", + "name": "Show performance warnings", + "role": "check box", + "label": "Click Performance a second time" + }, + { + "do": "click", + "name": "Show information", + "role": "check box", + "label": "Click Information a second time" + }, + { + "do": "click", + "name": "Cppcheck", + "role": "check box", + "label": "Click Cppcheck a second time" + }, + { + "do": "click", + "name": "Clang", + "role": "check box", + "label": "Click Clang a second time" + }, + { + "expect": "state", + "name": "Show errors", + "role": "check box", + "state": "checked", + "label": "Error is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show warnings", + "role": "check box", + "state": "checked", + "label": "Warning is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show style warnings", + "role": "check box", + "state": "checked", + "label": "Style is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show portability", + "role": "check box", + "state": "checked", + "label": "Portability is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show performance warnings", + "role": "check box", + "state": "checked", + "label": "Performance is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show information", + "role": "check box", + "state": "checked", + "label": "Information is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Cppcheck", + "role": "check box", + "state": "checked", + "label": "Cppcheck is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Clang", + "role": "check box", + "state": "checked", + "label": "Clang is back to its start state (checked)" + } + ], + "script_for": "6a0b05c5f781" + }, + { + "name": "Filter the results", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Type \"arrayIndex\" in the Filter field.", + "Clear the Filter field." + ], + "expected": "While the filter is \"arrayIndex\" only the arrayIndexOutOfBounds error in b.c is shown. After clearing it all errors are shown again.", + "id": "td7decf1a", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and results are shown" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "All files are listed before filtering" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "label": "util.h is listed before filtering" + }, + { + "do": "focus", + "path": "0.4.1", + "role": "text", + "label": "Focus the Filter field" + }, + { + "do": "type", + "value": "arrayIndex", + "label": "Type arrayIndex in the Filter field" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "label": "Only the b.c error remains shown" + }, + { + "expect": "gone", + "name": "src/a.c", + "role": "table cell", + "label": "a.c error is filtered out" + }, + { + "expect": "gone", + "name": "include/util.h", + "role": "table cell", + "label": "util.h error is filtered out" + }, + { + "do": "settext", + "path": "0.4.1", + "role": "text", + "value": "", + "label": "Clear the Filter field" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "a.c error is shown again" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "label": "b.c error is shown again" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "label": "util.h error is shown again" + } + ], + "script_for": "f8d406828551" + }, + { + "name": "Expand all", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Open View > Collapse All and then View > Expand All." + ], + "expected": "All files are expanded and show their errors: the errors in a.c, util.h and b.c are visible.", + "id": "t2af9d498", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "Checking src/b.c", + "role": "list item", + "timeout": 30, + "label": "Wait until the analysis has run" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "Results are shown" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Collapse all", + "role": "menu item", + "label": "View > Collapse All" + }, + { + "do": "click", + "name": "Expand all", + "role": "menu item", + "label": "View > Expand All (menu is still open)" + }, + { + "expect": "visible", + "name": "Null pointer dereference: r", + "role": "table cell", + "label": "The error in util.h is visible" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The error in a.c is visible" + }, + { + "expect": "state", + "name": "include/util.h", + "role": "table cell", + "state": "expanded", + "nth": 0, + "label": "util.h is expanded" + }, + { + "expect": "state", + "name": "src/a.c", + "role": "table cell", + "state": "expanded", + "nth": 0, + "label": "a.c is expanded" + } + ], + "script_for": "e38cfdd0677d" + }, + { + "name": "Collapse all", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Open View > Expand All and then View > Collapse All." + ], + "expected": "All files are collapsed: the file entries are shown and their errors are not.", + "id": "tec898362", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "File dialog appears" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 60, + "label": "Analysis is done and the file entries are shown" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Expand all", + "role": "menu item", + "label": "View > Expand All" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "Errors are visible after expanding" + }, + { + "do": "hover", + "name": "View", + "role": "menu item", + "label": "Open the View menu again" + }, + { + "do": "click", + "name": "Collapse all", + "role": "menu item", + "label": "View > Collapse All" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "label": "File entry util.h is shown" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "File entry a.c is shown" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "label": "File entry b.c is shown" + }, + { + "expect": "gone", + "name": "nullPointer", + "label": "Errors are not shown" + }, + { + "expect": "gone", + "name": "constVariablePointer", + "label": "Style errors are not shown" + } + ], + "script_for": "fc83a8d2b4c1" + }, + { + "name": "Context menu of a file entry", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Right click on the file a.c in the results (not on one of its errors)." + ], + "expected": "The context menu of the file entry has the entries Recheck and Open containing folder. It has none of the entries Hide, Hide all with ID or Suppress selected id, they only apply to a single error.", + "id": "t4c027507", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File from the File menu" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "Wait for the file dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "src/b.c", + "role": "table cell", + "timeout": 60, + "label": "Wait until the analysis is done and results are shown" + }, + { + "do": "wait", + "name": "Select Project File", + "gone": true, + "label": "File dialog is closed" + }, + { + "do": "rightclick", + "name": "src/a.c", + "role": "table cell", + "label": "Right click on the file a.c in the results" + }, + { + "expect": "ocr", + "contains": "Recheck", + "label": "Context menu has Recheck" + }, + { + "expect": "ocr", + "contains": "Open containing folder", + "label": "Context menu has Open containing folder" + }, + { + "expect": "ocr", + "not_contains": "Hide", + "label": "Context menu has no Hide entries" + }, + { + "expect": "ocr", + "not_contains": "Suppress selected id", + "label": "Context menu has no Suppress selected id" + } + ], + "script_for": "e2522fcc1afb" + }, + { + "name": "Hide one error", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Expand a.c in the results.", + "Right click on the first nullPointer error (line 6) and click Hide." + ], + "expected": "That error is gone. The other errors are still shown, including the second nullPointer error in a.c (line 12).", + "id": "taacc3841", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and results show a.c" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "Focus the a.c row" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "First nullPointer error (line 6) is shown" + }, + { + "do": "rightclick", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "Right click on the first nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Return" + ], + "label": "Click Hide (Hide 1 result(s)) in the context menu" + }, + { + "expect": "gone", + "name": "Null pointer dereference: p", + "label": "The hidden error is gone" + }, + { + "expect": "visible", + "name": "Null pointer dereference: q", + "role": "table cell", + "label": "Second nullPointer error (line 12) is still shown" + }, + { + "expect": "visible", + "name": "Variable 'p' can be declared as pointer to const", + "role": "table cell", + "label": "Other errors are still shown (line 5)" + }, + { + "expect": "visible", + "name": "Variable 'q' can be declared as pointer to const", + "role": "table cell", + "label": "Other errors are still shown (line 11)" + } + ], + "script_for": "f2771f5855a0" + }, + { + "name": "Hide all errors with the same ID", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Expand a.c in the results.", + "Right click on a nullPointer error and click Hide all with ID." + ], + "expected": "All nullPointer errors are gone, in a.c and in util.h. The arrayIndexOutOfBounds error in b.c is still shown.", + "id": "t2937b690", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "Wait until the analysis is done and results are shown" + }, + { + "do": "doubleclick", + "name": "src/a.c", + "role": "table cell", + "label": "Select a.c in the results" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "nullPointer errors are listed before hiding" + }, + { + "do": "rightclick", + "name": "nullPointer", + "role": "table cell", + "nth": 0, + "label": "Right click on a nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Down" + ], + "label": "Move to Hide all with id in the context menu" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Click Hide all with ID" + }, + { + "expect": "gone", + "name": "nullPointer", + "role": "table cell", + "label": "All nullPointer errors are gone from a.c and util.h" + }, + { + "expect": "visible", + "name": "constVariablePointer", + "role": "table cell", + "label": "Other errors in a.c are still shown" + }, + { + "do": "key", + "keys": [ + "End", + "Right" + ], + "label": "Expand b.c in the results" + }, + { + "expect": "visible", + "name": "arrayIndexOutOfBounds", + "role": "table cell", + "label": "The arrayIndexOutOfBounds error in b.c is still shown" + } + ], + "script_for": "e220a9a4c618" + }, + { + "name": "Show hidden errors again", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Expand a.c in the results.", + "Right click on the first nullPointer error and click Hide.", + "Open View > Show Hidden." + ], + "expected": "The error that was hidden is shown again.", + "id": "t313463d3", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "nth": 1, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and results are shown" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the analysis finish completely" + }, + { + "do": "click", + "name": "src/a.c", + "role": "table cell", + "mouse": true, + "label": "Select a.c in the results" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The first nullPointer error is listed" + }, + { + "do": "rightclick", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "Right click on the first nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Return" + ], + "label": "Click Hide in the context menu" + }, + { + "expect": "gone", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The error is hidden" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Show hidden", + "role": "menu item", + "label": "Click Show Hidden" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "timeout": 5, + "observed_fail": true, + "label": "The hidden error is shown again" + } + ], + "script_for": "0a6d5d24428f" + }, + { + "name": "Suppress selected ID", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Expand a.c in the results.", + "Right click on a nullPointer error and click Suppress selected id.", + "Open File > Edit Project File and go to Warning options." + ], + "expected": "No nullPointer error is shown in the results. The id nullPointer is listed under Suppressions in the project file dialog.", + "id": "t90bf32e9", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Click Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 60, + "label": "Wait until the analysis is done" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the analysis finish" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "Select a.c in the results" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "nullPointer error is shown under a.c" + }, + { + "do": "rightclick", + "name": "nullPointer", + "role": "table cell", + "nth": 0, + "label": "Right click on a nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Down", + "Down", + "Return" + ], + "label": "Click Suppress selected id" + }, + { + "expect": "gone", + "name": "nullPointer", + "role": "table cell", + "label": "No nullPointer error is shown in the results" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open File menu" + }, + { + "do": "click", + "name": "Edit Project File", + "role": "menu item", + "label": "Click Edit Project File" + }, + { + "do": "click", + "name": "Warning options", + "role": "page tab", + "label": "Go to Warning options" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "list item", + "label": "The id nullPointer is listed under Suppressions" + } + ], + "script_for": "a86c68038052" + }, + { + "name": "Clear results", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Click Clear results." + ], + "expected": "The results are cleared: the result tree is empty.", + "id": "t389a5f3a", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis results are shown" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Wait until the analysis is done" + }, + { + "expect": "state", + "name": "Clear results", + "role": "button", + "state": "enabled", + "label": "Analysis finished, Clear results is available" + }, + { + "do": "click", + "name": "Clear results", + "role": "button", + "label": "Click Clear results" + }, + { + "expect": "gone", + "name": "src/a.c", + "role": "table cell", + "label": "Result tree has no src/a.c entry" + }, + { + "expect": "gone", + "name": "src/b.c", + "role": "table cell", + "label": "Result tree has no src/b.c entry" + }, + { + "expect": "gone", + "name": "include/util.h", + "role": "table cell", + "label": "Result tree has no include/util.h entry" + } + ], + "script_for": "2f1832fabb0d" + } + ] +} diff --git a/gui/manualtest/tests/xml_results.json b/gui/manualtest/tests/xml_results.json new file mode 100644 index 00000000000..e6ba4db7a59 --- /dev/null +++ b/gui/manualtest/tests/xml_results.json @@ -0,0 +1,458 @@ +{ + "title": "XML results", + "tests": [ + { + "name": "Save results to a file", + "projects": [ + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", + "Open File > Save results to file.", + "Choose the file type for {format} files and save the results as results.{format} in the work directory {work}." + ], + "expected": "The file results.{format} is saved in the {format} format. It has the nullPointer error in a.c (\"Null pointer dereference\") and the arrayIndexOutOfBounds error in b.c (\"out of bounds\").", + "id": "tc1fc409e", + "script": [ + { + "label": "Start the application with no project loaded", + "expect": "visible", + "name": "Cppcheck", + "role": "frame" + }, + { + "label": "Open the File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Choose File > Open Project File", + "do": "click", + "name": "Open Project File...", + "role": "menu item" + }, + { + "label": "Wait for the project file dialog", + "do": "wait", + "name": "Select Project File", + "role": "dialog" + }, + { + "label": "Enter the project file basic.cppcheck", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck" + }, + { + "label": "Open the project file", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "The project is loaded", + "expect": "visible", + "name": "Project: basic.cppcheck", + "role": "frame", + "timeout": 10 + }, + { + "label": "Analysis has produced results for b.c", + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30 + }, + { + "label": "Wait until the analysis is done", + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 30 + }, + { + "label": "Open the File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Open File > Save results to file", + "do": "click", + "name": "Save results to file...", + "role": "menu item" + }, + { + "label": "Wait for the save dialog", + "do": "wait", + "name": "Save the report file", + "role": "dialog" + }, + { + "label": "Open the Files of type list", + "do": "click", + "name": "XML files", + "role": "combo box", + "mouse": true + }, + { + "label": "Choose the file type for {format} files", + "do": "click", + "name": "(*.{format})", + "role": "list item", + "mouse": true + }, + { + "label": "Enter results.{format} in the work directory as file name", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{work}/results.{format}" + }, + { + "label": "Save the results", + "do": "click", + "name": "Save", + "role": "button", + "exact": true + }, + { + "label": "The save dialog is closed", + "expect": "gone", + "name": "Save the report file", + "role": "dialog" + }, + { + "label": "results.{format} is saved and names a.c", + "expect": "file", + "path": "{work}/results.{format}", + "contains": "src/a.c" + }, + { + "label": "results.{format} has the nullPointer error in a.c (Null pointer dereference)", + "expect": "file", + "path": "{work}/results.{format}", + "contains": "Null pointer dereference" + }, + { + "label": "results.{format} has the arrayIndexOutOfBounds error in b.c (out of bounds)", + "expect": "file", + "path": "{work}/results.{format}", + "regex": "src/b.c[\\s\\S]*out of bounds|out of bounds[\\s\\S]*src/b.c" + } + ], + "script_for": "d95f3ebb3c39", + "matrix": { + "format": [ + "xml", + "txt", + "csv" + ] + } + }, + { + "name": "Open XML results", + "projects": [ + "xml-results" + ], + "steps": [ + "Start the application with no project loaded.", + "Open File > Open XML.", + "Select {project:xml-results}/results.xml." + ], + "expected": "The XML results are opened: the nullPointer error in a.c is shown.", + "id": "tcdb051d4", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open XML", + "role": "menu item", + "label": "Choose File > Open XML" + }, + { + "expect": "visible", + "name": "Open the report file", + "role": "dialog", + "label": "The file dialog is shown" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:xml-results}/results.xml", + "label": "Select results.xml of the xml-results project" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "Open the report file", + "gone": true, + "label": "The file dialog closes" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "label": "The file a.c is listed in the results" + }, + { + "do": "focus", + "name": "a.c", + "role": "table cell", + "label": "Give keyboard focus to the a.c entry" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand the a.c entry to see its error" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "The nullPointer error is shown" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The nullPointer summary is shown" + }, + { + "expect": "visible", + "name": "error", + "role": "table cell", + "label": "The severity of the error is shown" + } + ], + "script_for": "166851745b45" + }, + { + "name": "No Recheck in XML results", + "ticket": "#13984", + "projects": [ + "xml-results" + ], + "steps": [ + "Start the application with no project loaded.", + "Open File > Open XML and select {project:xml-results}/results.xml.", + "Right click on the file a.c in the results." + ], + "expected": "The context menu of the file has no Recheck entry and no Open containing folder entry, XML results can not be analysed again. The application does not crash.", + "id": "t8d9a2076", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open XML", + "role": "menu item", + "label": "Choose File > Open XML" + }, + { + "do": "wait", + "name": "Open the report file", + "timeout": 10, + "label": "Wait for the open dialog" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:xml-results}/results.xml", + "label": "Enter the path of results.xml" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "nth": 1, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 10, + "label": "The results show the file a.c" + }, + { + "do": "rightclick", + "name": "a.c", + "role": "table cell", + "label": "Right click on the file a.c in the results" + }, + { + "do": "key", + "keys": [ + "Menu" + ], + "label": "Make sure the context menu is open" + }, + { + "expect": "gone", + "name": "Recheck", + "label": "The context menu has no Recheck entry" + }, + { + "expect": "gone", + "name": "Open containing folder", + "label": "The context menu has no Open containing folder entry" + }, + { + "expect": "alive", + "label": "The application does not crash" + } + ], + "script_for": "dd4444ebf37c" + }, + { + "name": "Reanalyze enabled after XML results", + "projects": [ + "xml-results", + "basic-project" + ], + "steps": [ + "Start the application with no project loaded.", + "Open File > Open XML and select {project:xml-results}/results.xml.", + "Open the project file {project:basic-project}/basic.cppcheck and wait until the analysis is done." + ], + "expected": "The button Reanalyze all files is enabled and can be clicked.", + "id": "t053b7c9c", + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open XML", + "role": "menu item", + "label": "Choose File > Open XML" + }, + { + "do": "wait", + "name": "Open the report file", + "timeout": 10, + "label": "Wait for the Open XML dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:xml-results}/results.xml", + "label": "Enter the path of results.xml" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the XML file" + }, + { + "do": "wait", + "name": "Open the report file", + "gone": true, + "timeout": 10, + "label": "Wait for the dialog to close" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu again" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose File > Open Project File" + }, + { + "do": "wait", + "name": "Select Project File", + "timeout": 10, + "label": "Wait for the project file dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "do": "wait", + "name": "Select Project File", + "gone": true, + "timeout": 10, + "label": "Wait for the dialog to close" + }, + { + "do": "sleep", + "seconds": 5, + "label": "Wait until the analysis is done" + }, + { + "expect": "visible", + "name": "Reanalyze all files", + "role": "button", + "timeout": 10, + "label": "Reanalyze all files button is shown" + }, + { + "expect": "state", + "name": "Reanalyze all files", + "role": "button", + "state": "enabled", + "timeout": 10, + "label": "Reanalyze all files button is enabled" + }, + { + "do": "click", + "name": "Reanalyze all files", + "role": "button", + "label": "Click Reanalyze all files" + }, + { + "expect": "alive", + "label": "Application still running after the click" + } + ], + "script_for": "c34fa36b12bd" + } + ] +} diff --git a/gui/manualtest/web.py b/gui/manualtest/web.py new file mode 100644 index 00000000000..0edacb07dbd --- /dev/null +++ b/gui/manualtest/web.py @@ -0,0 +1,462 @@ +#!/usr/bin/env python3 +"""Web interface for editing, generating and running the JSON tests. + + web.py [--opensource APP] [--premium APP] [--src DIR] [--dir tests] [--out results] [--port 8080] + +Serves on 127.0.0.1 only. Reads and writes DIR/*.json and starts gen.py and run.py with the +application that is tested, one binary per edition. SRC is the git checkout that both are built +from, for the git sha in the reports. --app is the same as --opensource. Without any APP the +tests can be edited but not generated or run. +""" + +import argparse +import json +import mimetypes +import os +import re +import signal +import subprocess +import sys +import shutil +import threading +import time +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from urllib.parse import parse_qs, quote, unquote, urlparse + +import testfile + +HERE = os.path.dirname(os.path.abspath(__file__)) +NAME_RE = re.compile(r'^[\w.-]+\.json$') +ID_RE = re.compile(r'^\w+$') + + +class Job: + """gen.py or run.py in the background, one at a time. The output is shown in the web page. + A run of several editions is one run.py after the other, each with its own report.""" + + def __init__(self, kind, file, test_id, cmds, out_dir): + self.kind, self.file, self.test_id, self.out_dir = kind, file, test_id, out_dir + self.editions = [e for e, _ in cmds] + self.lines, self.reports, self.rc = [], [], None + self.started = time.time() + self.progress = {'total': 0, 'index': 0, 'test': '', 'step': 0, 'steps': 0, 'label': '', 'base': None, + 'edition': None, 'results': []} + self.proc, self.stopped, self.lock = None, False, threading.Lock() + threading.Thread(target=self.work, args=(cmds,), daemon=True).start() + + def work(self, cmds): + rc = 0 + for edition, cmd in cmds: + with self.lock: + if self.stopped: + break + self.edition = edition + self.proc = subprocess.Popen(cmd, cwd=HERE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, + env=dict(os.environ, PYTHONUNBUFFERED='1'), start_new_session=True) + if len(cmds) > 1: + self.lines.append('=== %s ===' % edition) + self.read() + rc = max(rc, self.proc.wait()) + self.rc = rc + + def stop(self): + with self.lock: + self.stopped = True + if self.proc and self.proc.poll() is None: + self.proc.send_signal(signal.SIGINT) # run.py writes its report, gen.py cleans up + + def read(self): + for line in self.proc.stdout: + line = line.rstrip('\n') + if line.startswith('@{') and self.event(line[1:]): + continue + self.lines.append(line) + m = re.match(r'report: (.+)$', line) + if m: + self.reports.append({'edition': self.edition, + 'url': '/results/' + os.path.relpath(m.group(1), self.out_dir)}) + + def event(self, text): + """A progress line from run.py --events. False when it is not one.""" + try: + e = json.loads(text) + kind, p = e['kind'], self.progress + except (ValueError, KeyError, TypeError): + return False + if kind == 'start': + p.update(total=e['total'], index=0, test='', step=0, steps=0, label='', edition=e.get('edition'), + base='/results/' + os.path.relpath(e['out'], self.out_dir)) + elif kind == 'test': + p.update(index=e['index'], test=e['name'], step=0, steps=e['steps'], label='') + elif kind == 'step': + p.update(step=e['step'], steps=e['steps'], label=e['label']) + elif kind == 'result': + p['results'].append(dict({k: e[k] for k in ('name', 'status', 'reason', 'seconds', 'crash', 'slug')}, + shots=e.get('shots', []), edition=p['edition'], base=p['base'])) + return True + + def info(self): + return {'kind': self.kind, 'file': self.file, 'id': self.test_id, 'running': self.rc is None, + 'rc': self.rc, 'output': '\n'.join(self.lines), 'reports': self.reports, 'editions': self.editions, + 'progress': self.progress, 'started': self.started, 'now': time.time()} + + +class Handler(BaseHTTPRequestHandler): + directory = None + out_dir = None + apps = {} # edition -> path of the binary + src = None + job = None + lock = threading.Lock() + + def log_message(self, fmt, *args): + pass + + def reply(self, code, data, ctype='application/json'): + if ctype == 'application/json': + data = json.dumps(data, ensure_ascii=False) + if isinstance(data, str): + data = data.encode() + self.send_response(code) + self.send_header('Content-Type', ctype + ('; charset=utf-8' if ctype.startswith(('text/', 'application/json')) else '')) + self.send_header('Content-Length', str(len(data))) + self.send_header('Cache-Control', 'no-store') + self.end_headers() + self.wfile.write(data) + + def allowed(self): + # only localhost names as Host (DNS rebinding) and JSON for writes (cross-site forms) + host = self.headers.get('Host', '').rsplit(':', 1)[0] + if host not in ('127.0.0.1', 'localhost'): + self.reply(403, {'error': 'bad host'}) + return False + if self.command in ('PUT', 'POST') and self.headers.get('Content-Type', '').split(';')[0] != 'application/json': + self.reply(415, {'error': 'application/json required'}) + return False + return True + + def path_of(self, query): + name = parse_qs(query).get('name', [''])[0] + if not NAME_RE.match(name): + self.reply(400, {'error': 'bad file name (letters, digits, _ - . and .json)'}) + return None + return os.path.join(self.directory, name) + + def project_path(self, query): + name = parse_qs(query).get('name', [''])[0] + if not testfile.PROJECT_RE.match(name): + self.reply(400, {'error': 'bad project name (letters, digits, _ and -)'}) + return None + return os.path.join(self.directory, testfile.PROJECTS, name) + + def used_by(self, project): + used = [] + for f in sorted(os.listdir(self.directory)): + if NAME_RE.match(f): + try: + tests = testfile.load(os.path.join(self.directory, f)).get('tests', []) + except (ValueError, OSError): + continue + used += ['%s: %s' % (f, t.get('name')) for t in tests if project in t.get('projects', [])] + return used + + def reports(self): + """The earlier runs in the out directory, newest first.""" + found = [] + for d in os.listdir(self.out_dir) if os.path.isdir(self.out_dir) else []: + if not os.path.isfile(os.path.join(self.out_dir, d, 'report.html')): + continue + try: + with open(os.path.join(self.out_dir, d, 'results.json'), encoding='utf-8') as f: + run = json.load(f) + counts = {} + for r in run.get('results', []): + counts[r['status']] = counts.get(r['status'], 0) + 1 + except (ValueError, OSError, KeyError, TypeError): + run, counts = {}, {} + found.append({'id': d, 'url': '/results/%s/report.html' % quote(d), 'started': run.get('started', ''), + 'edition': run.get('edition'), 'branch': run.get('branch'), 'sha': (run.get('sha') or '')[:8], + 'dirty': run.get('dirty'), 'aborted': run.get('aborted'), 'counts': counts, + 'files': [f.get('title') or f.get('file') for f in run.get('files', [])]}) + return sorted(found, key=lambda r: r['id'], reverse=True) + + def annotate(self, doc): + """Add _stale (the script is outdated) and _errors (what is wrong with the test or its script) + to every test, for the web page. Not saved, see stamp_scripts.""" + tests = doc.get('tests') if isinstance(doc, dict) else None + for t in tests if isinstance(tests, list) else []: + if not isinstance(t, dict): + continue + t['_stale'] = testfile.is_stale(t, self.directory) + t['_errors'] = testfile.validate({'tests': [t]}, self.directory) + + def file_status(self): + """{file: {invalid: n, stale: n}}: tests with errors and tests whose script must be generated again. + invalid is 1 for a file that is not valid JSON or not a test file.""" + out = {} + for name in os.listdir(self.directory): + if not NAME_RE.match(name): + continue + try: + with open(os.path.join(self.directory, name), encoding='utf-8') as f: + doc = testfile.ensure_ids(json.load(f)) + self.annotate(doc) + tests = [t for t in doc['tests'] if isinstance(t, dict)] + bad = sum(1 for t in tests if t['_errors']) + (len(tests) != len(doc['tests'])) + out[name] = {'invalid': bad, 'stale': sum(1 for t in tests if t['_stale'])} + except (ValueError, OSError, KeyError, TypeError, AttributeError): + out[name] = {'invalid': 1, 'stale': 0} + return out + + def body(self): + return json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))) or b'null') + + def do_GET(self): + if not self.allowed(): + return + u = urlparse(self.path) + if u.path == '/': + with open(os.path.join(HERE, 'web', 'index.html'), 'rb') as f: + self.reply(200, f.read(), 'text/html') + elif u.path == '/api/files': + self.reply(200, sorted(f for f in os.listdir(self.directory) if NAME_RE.match(f))) + elif u.path == '/api/file-status': + self.reply(200, self.file_status()) + elif u.path == '/api/script-doc': + self.reply(200, testfile.SCRIPT_DOC, 'text/plain') + elif u.path == '/api/config': + self.reply(200, {'apps': self.apps, 'editions': list(testfile.EDITIONS), 'can_run': bool(self.apps)}) + elif u.path == '/api/job': + self.reply(200, self.job.info() if self.job else None) + elif u.path == '/api/projects': + base = os.path.join(self.directory, testfile.PROJECTS) + names = sorted(n for n in os.listdir(base) if os.path.isdir(os.path.join(base, n))) if os.path.isdir(base) else [] + self.reply(200, [{'name': n, 'used_by': self.used_by(n)} for n in names]) + elif u.path == '/api/project': + path = self.project_path(u.query) + if path: + if not os.path.isdir(path): + return self.reply(404, {'error': 'no such project'}) + files, binary = {}, [] + for rel, data in sorted(testfile.project_files(self.directory, os.path.basename(path)).items()): + try: + files[rel] = data.decode('utf-8') + except UnicodeDecodeError: + binary.append(rel) + self.reply(200, {'files': files, 'binary': binary, 'used_by': self.used_by(os.path.basename(path))}) + elif u.path == '/api/reports': + self.reply(200, self.reports()) + elif u.path.startswith('/results/'): + self.static_file(self.out_dir, unquote(u.path[len('/results/'):])) + elif u.path.startswith('/docs/'): + self.static_file(os.path.join(HERE, 'docs'), unquote(u.path[len('/docs/'):])) + elif u.path == '/api/file': + path = self.path_of(u.query) + if path: + try: + with open(path, encoding='utf-8') as f: + doc = testfile.ensure_ids(json.load(f)) + except FileNotFoundError: + return self.reply(404, {'error': 'no such file'}) + except ValueError as e: + return self.reply(400, {'error': '%s is not valid JSON: %s' % (os.path.basename(path), e)}) + self.annotate(doc) + self.reply(200, doc) + else: + self.reply(404, {'error': 'not found'}) + + def static_file(self, base, rel): + """A file below BASE: the results of the runs or the handbook.""" + base = os.path.realpath(base) + path = os.path.realpath(os.path.join(base, rel)) + if not path.startswith(base + os.sep) or not os.path.isfile(path): + return self.reply(404, {'error': 'not found'}) + ctype = 'text/plain' if path.endswith('.log') else mimetypes.guess_type(path)[0] or 'text/plain' + with open(path, 'rb') as f: + self.reply(200, f.read(), ctype) + + def do_PUT(self): + if not self.allowed(): + return + u = urlparse(self.path) + if u.path == '/api/project': + return self.put_project(u.query) + path = self.path_of(u.query) if u.path == '/api/file' else None + if not path: + return + try: + doc = self.body() + except ValueError: + return self.reply(400, {'errors': ['not valid JSON']}) + errors = testfile.validate(doc, self.directory) + if errors: + return self.reply(400, {'errors': errors}) + try: + old = testfile.load(path) + except (FileNotFoundError, ValueError): + old = None + testfile.save(path, testfile.stamp_scripts(doc, old, self.directory)) + self.reply(200, {'saved': os.path.basename(path)}) + + def put_project(self, query): + path = self.project_path(query) + if not path: + return + try: + files = self.body()['files'] + assert isinstance(files, dict) and all(isinstance(v, str) for v in files.values()) + except (ValueError, KeyError, TypeError, AssertionError): + return self.reply(400, {'errors': ['expected {"files": {"path": "content"}}']}) + clean = {} + for rel, content in files.items(): + norm = os.path.normpath(rel) + if (not rel or os.path.isabs(norm) or norm.startswith('..') or '\0' in rel or norm == '.' + or norm.split(os.sep)[0] == '..'): + return self.reply(400, {'errors': ['bad file path: %r (relative, inside the project)' % rel]}) + clean[norm] = content + if sum(len(c) for c in clean.values()) > 4_000_000: + return self.reply(400, {'errors': ['the project is too big for this editor']}) + os.makedirs(path, exist_ok=True) + # text files that are not in the request were removed in the editor, binary files stay + for rel, data in testfile.project_files(self.directory, os.path.basename(path)).items(): + if rel not in clean: + try: + data.decode('utf-8') + except UnicodeDecodeError: + continue + os.remove(os.path.join(path, rel)) + for rel, content in clean.items(): + target = os.path.join(path, rel) + os.makedirs(os.path.dirname(target), exist_ok=True) + with open(target + '.tmp', 'w', encoding='utf-8', newline='') as f: + f.write(content) + os.replace(target + '.tmp', target) + for dirpath, dirs, fs in os.walk(path, topdown=False): # directories that became empty + if dirpath != path and not os.listdir(dirpath): + os.rmdir(dirpath) + self.reply(200, {'saved': os.path.basename(path), 'files': len(clean)}) + + def do_POST(self): + if not self.allowed(): + return + route = urlparse(self.path).path + try: + data = self.body() + if route == '/api/import': + self.reply(200, testfile.from_markdown(data['markdown'])) + elif route == '/api/job': + self.start_job(data) + elif route == '/api/job/stop': + if self.job and self.job.rc is None: + self.job.stop() + self.reply(200, {'stopping': True}) + else: + self.reply(404, {'error': 'not found'}) + except (ValueError, KeyError, TypeError): + self.reply(400, {'errors': ['bad request']}) + + def start_job(self, data): + action, name, test_id, edition = data['action'], data['file'], data.get('id'), data.get('edition') + if not self.apps: + return self.reply(400, {'errors': ['web.py was started without --opensource or --premium']}) + everything = action == 'run' and name == '*' # every test file in the directory + if action not in ('generate', 'run') or not (everything or NAME_RE.match(name)) or ( + test_id and not ID_RE.match(test_id)): + return self.reply(400, {'errors': ['bad request']}) + if action == 'generate' and not test_id: + return self.reply(400, {'errors': ['generate needs a test id']}) + if everything: + paths = sorted(os.path.join(self.directory, n) for n in os.listdir(self.directory) if NAME_RE.match(n)) + if not paths or test_id: + return self.reply(404, {'errors': ['no test files']}) + else: + paths = [os.path.join(self.directory, name)] + if not os.path.isfile(paths[0]): + return self.reply(404, {'errors': ['no such file']}) + path = paths[0] + # the editions of the job: the chosen ones that web.py has a binary for and, for one test, that it is for + editions = [e for e in testfile.EDITIONS if e in self.apps and edition in (e, 'both')] + if test_id: + test = next((t for t in testfile.ensure_ids(testfile.load(path)).get('tests', []) if t['id'] == test_id), None) + if not test: + return self.reply(404, {'errors': ['no such test']}) + allowed = [e for e in testfile.editions(test) if e in self.apps] + if action == 'generate' and not set(editions) & set(allowed): + editions = allowed # the script is the same for all editions, make it with one that we have + editions = [e for e in editions if e in allowed] + if not editions: + return self.reply(400, {'errors': ['the test is not for the edition %s, or web.py has no application for it' % (edition or '')]}) + if action == 'generate': + editions = editions[:1] + cmds = [] + for e in editions: + app = self.apps[e] + if action == 'generate': + cmd = [sys.executable, 'gen.py', app, path, '--force', '--id', test_id, '--edition', e] + else: + cmd = [sys.executable, 'run.py', app, *paths, '--out', self.out_dir, '--events', '--edition', e] + cmd += ['--src', self.src] if self.src else [] + cmd += ['--id', test_id] if test_id else [] + cmds.append((e, cmd)) + with self.lock: + if self.job and self.job.rc is None: + return self.reply(409, {'errors': ['a job is already running']}) + Handler.job = Job(action, name, test_id, cmds, self.out_dir) + self.reply(200, self.job.info()) + + def do_DELETE(self): + if not self.allowed(): + return + u = urlparse(self.path) + if u.path == '/api/project': + path = self.project_path(u.query) + if path: + used = self.used_by(os.path.basename(path)) + if used: + return self.reply(409, {'errors': ['the project is used by: ' + '; '.join(used)]}) + if not os.path.isdir(path): + return self.reply(404, {'error': 'no such project'}) + shutil.rmtree(path) + self.reply(200, {'deleted': os.path.basename(path)}) + return + path = self.path_of(u.query) if u.path == '/api/file' else None + if path: + try: + os.remove(path) + self.reply(200, {'deleted': os.path.basename(path)}) + except FileNotFoundError: + self.reply(404, {'error': 'no such file'}) + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + for e in testfile.EDITIONS: + ap.add_argument('--' + e, *(['--app'] if e == 'opensource' else []), metavar='APP', + help='the %s edition of the application to test' % e) + ap.add_argument('--src', help='git checkout of the application, the same for all editions (default: the directory of APP)') + ap.add_argument('--dir', default=os.path.join(HERE, 'tests')) + ap.add_argument('--out', default=os.path.join(HERE, 'results')) + ap.add_argument('--port', type=int, default=8080) + args = ap.parse_args() + for e in testfile.EDITIONS: + app = getattr(args, e) + if app: + if not os.access(app, os.X_OK): + sys.exit('error: not executable: ' + app) + Handler.apps[e] = os.path.abspath(app) + Handler.src = os.path.abspath(args.src) if args.src else None + Handler.directory = os.path.abspath(args.dir) + Handler.out_dir = os.path.realpath(args.out) + os.makedirs(Handler.directory, exist_ok=True) + os.makedirs(Handler.out_dir, exist_ok=True) + Handler.out_dir = os.path.realpath(Handler.out_dir) + server = ThreadingHTTPServer(('127.0.0.1', args.port), Handler) + print('http://127.0.0.1:%d/ (%s)' % (args.port, Handler.directory), flush=True) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + + +if __name__ == '__main__': + main() diff --git a/gui/manualtest/web/index.html b/gui/manualtest/web/index.html new file mode 100644 index 00000000000..6ef99566a27 --- /dev/null +++ b/gui/manualtest/web/index.html @@ -0,0 +1,648 @@ + + + + + +GUI tester + + + +
+

GUI tester

+ no file selected + + + + + + Handbook +
+
+ +

Select a file on the left or create a new one.

+
+
+ + + From bbe4976370e0e047209ea8067ff150cd2cf571a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 24 Oct 2026 09:26:05 +0200 Subject: [PATCH 2/6] gui manualtest: write the tests in Gherkin The tests are now Gherkin feature files (tests/*.feature): Background, Scenario with Given/When/Then steps, Scenario Outline with Examples instead of the test matrix, and tags for id, ticket, test projects, editions and disabled. The generated scripts are kept next to each feature file in NAME.scripts.json, keyed by test id. testfile.py parses and writes the feature files (round trip is exact) and converts older JSON and markdown test files. The web editor, gen.py, run.py and the handbook are updated for the new format. Co-Authored-By: Claude Opus 5.5 --- gui/manualtest/docs/handbook.html | 64 +- gui/manualtest/gen.py | 35 +- gui/manualtest/run.py | 10 +- gui/manualtest/testfile.py | 499 +++++-- gui/manualtest/tests/analysis.feature | 55 + gui/manualtest/tests/analysis.json | 457 ------- gui/manualtest/tests/analysis.scripts.json | 399 ++++++ gui/manualtest/tests/application.feature | 59 + gui/manualtest/tests/application.json | 607 --------- gui/manualtest/tests/application.scripts.json | 548 ++++++++ gui/manualtest/tests/preferences.feature | 42 + gui/manualtest/tests/preferences.json | 357 ----- gui/manualtest/tests/preferences.scripts.json | 306 +++++ gui/manualtest/tests/project_dialog.feature | 40 + gui/manualtest/tests/project_dialog.json | 428 ------ .../tests/project_dialog.scripts.json | 371 +++++ gui/manualtest/tests/projects.feature | 72 + gui/manualtest/tests/projects.json | 855 ------------ gui/manualtest/tests/projects.scripts.json | 771 +++++++++++ gui/manualtest/tests/result_list.feature | 90 ++ gui/manualtest/tests/result_list.json | 1198 ----------------- gui/manualtest/tests/result_list.scripts.json | 1081 +++++++++++++++ gui/manualtest/tests/xml_results.feature | 42 + gui/manualtest/tests/xml_results.json | 458 ------- gui/manualtest/tests/xml_results.scripts.json | 401 ++++++ gui/manualtest/web.py | 29 +- gui/manualtest/web/index.html | 157 ++- 27 files changed, 4874 insertions(+), 4557 deletions(-) create mode 100644 gui/manualtest/tests/analysis.feature delete mode 100644 gui/manualtest/tests/analysis.json create mode 100644 gui/manualtest/tests/analysis.scripts.json create mode 100644 gui/manualtest/tests/application.feature delete mode 100644 gui/manualtest/tests/application.json create mode 100644 gui/manualtest/tests/application.scripts.json create mode 100644 gui/manualtest/tests/preferences.feature delete mode 100644 gui/manualtest/tests/preferences.json create mode 100644 gui/manualtest/tests/preferences.scripts.json create mode 100644 gui/manualtest/tests/project_dialog.feature delete mode 100644 gui/manualtest/tests/project_dialog.json create mode 100644 gui/manualtest/tests/project_dialog.scripts.json create mode 100644 gui/manualtest/tests/projects.feature delete mode 100644 gui/manualtest/tests/projects.json create mode 100644 gui/manualtest/tests/projects.scripts.json create mode 100644 gui/manualtest/tests/result_list.feature delete mode 100644 gui/manualtest/tests/result_list.json create mode 100644 gui/manualtest/tests/result_list.scripts.json create mode 100644 gui/manualtest/tests/xml_results.feature delete mode 100644 gui/manualtest/tests/xml_results.json create mode 100644 gui/manualtest/tests/xml_results.scripts.json diff --git a/gui/manualtest/docs/handbook.html b/gui/manualtest/docs/handbook.html index 2bc1f6beba4..d81bd495115 100644 --- a/gui/manualtest/docs/handbook.html +++ b/gui/manualtest/docs/handbook.html @@ -58,7 +58,7 @@
  • Overview
  • Getting started
  • Writing tests -
    1. Test matrix
    2. Test projects
  • +
    1. Examples (test matrix)
    2. Test projects
  • Scripts
    1. Generating a script
    2. The script format
    3. Outdated scripts
  • Running tests
  • @@ -74,7 +74,7 @@

    gui-test Handbook

    Updated 2026-10-22

    Overview

    -

    A test is written in plain words: steps and an expected result. gen.py starts the application, lets Claude do the test by hand through guitest.py, and saves what it did as a script of do and expect steps. run.py replays the scripts, one fresh application per test, and writes an HTML report. web.py is the web interface around all of it.

    +

    A test is written in Gherkin: a scenario with Given, When and Then steps in plain words. gen.py starts the application, lets Claude do the test by hand through guitest.py, and saves what it did as a script of do and expect steps. run.py replays the scripts, one fresh application per test, and writes an HTML report. web.py is the web interface around all of it.

    @@ -90,7 +90,7 @@

    Overview

    A test in words - steps, expected result + Gherkin scenarios gen.py Claude does it once @@ -123,7 +123,7 @@

    Overview

    guitest.pyCommand line tool that starts the session and clicks, types and reads the GUI testfile.pyThe test file format, test matrices, test projects and outdated-script detection report.pyWrites the HTML report -tests/The test files (*.json) and test projects (tests/projects/NAME) +tests/The test files (*.feature), their scripts (*.scripts.json) and test projects (tests/projects/NAME) results/One directory per run with the report, logs and screenshots @@ -159,8 +159,20 @@

    The web interface

    Writing tests

    -

    A test is written as if for a person: steps in plain words and the result to expect. Claude reads exactly these words when it makes the script, so be concrete.

    -

    Tests live in test files (tests/*.json), one file per area of the GUI. Click a file in the left column to see its title and its tests, New file to create one, or Import .md to convert an older markdown test file (sections ## Test: NAME). Click a test in the list to edit it, + New test to add one. Nothing is written to disk until you press Save; a dot after the file name means there are unsaved changes.

    +

    A test is a Gherkin scenario, written as if for a person: Given steps say where the test starts, When steps what is done and Then steps the result to expect. Claude reads exactly these words when it makes the script, so be concrete.

    +
    Feature: Results list
    +
    +  Background:
    +    Given the application is started with no project loaded
    +
    +  @id:t389a5f3a @project:basic-project
    +  Scenario: Clear results
    +    Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu
    +    And the analysis is done
    +    When I click Clear results
    +    Then the results are cleared: the result tree is empty
    +

    A file is one Feature, its title is the title of the file. The Background steps belong to every scenario of the file. The fields that are not steps are tags above the scenario: @id:, @ticket:, @project: (one per project), @edition: (one per edition, none means all) and @disabled. A step can have a doc string (lines between """) or a table. The files can be edited in the web interface or in a text editor; comments (#) are lost when the web interface saves the file. The scripts are not in the feature file but next to it, in FILE.scripts.json, by test id.

    +

    Tests live in test files (tests/*.feature), one file per area of the GUI. Click a file in the left column to see its title and its tests, New file to create one, or Import .md to convert an older markdown test file (sections ## Test: NAME). Older JSON test files are converted with testfile.py convert IN.json OUT.feature. Click a test in the list to edit it, + New test to add one. Nothing is written to disk until you press Save; a dot after the file name means there are unsaved changes.

    @@ -168,15 +180,21 @@

    Writing tests

    - - - + + - +
    FieldWhat to write
    TicketOptional, e.g. a Trac ticket number
    enabledUntick to leave the test out of every run without deleting it
    EditionsThe editions the test runs in; default both. The script is the same for all, {edition} is replaced by the edition's name
    StepsOne step per line, e.g. "Open File > New project file". Use {axis} for matrix values
    Expected resultWhat must be true at the end. Every part becomes a check in the script
    TODOWhy a test can't be automated yet, e.g. "cannot be automated". Leave empty otherwise
    Scenario stepsOne Gherkin step per line, e.g. "When I open File > New Project File". Every Then step becomes at least one check in the script. Use <column> for the values of the Examples
    TODOWhy a test can't be automated yet, e.g. "cannot be automated". Leave empty otherwise. In the file it is a line TODO: ... below the scenario name
    Test projectsThe projects whose files the test uses, see below
    Test matrixAxes with comma-separated values; every combination is a separate run
    ExamplesA Gherkin table; every row is a separate run and the test becomes a Scenario Outline
    -

    Test matrix

    -

    An axis named platform with the values native, unix32 and an axis lang with C, C++ give 4 runs. A step like "Select platform {platform}" gets the value of each run. The runs are named after the test plus the values, e.g. Relative paths [platform=unix32, lang=C], and each one gets its own line in the report. The editor shows how many runs a matrix gives.

    +

    Examples (test matrix)

    +

    A Scenario Outline is run once for every row of its Examples table:

    +
        When I select the platform <platform>
    +    ...
    +    Examples:
    +      | platform | lang |
    +      | native   | C    |
    +      | unix32   | C++  |
    +

    That is 2 runs. A step like "When I select the platform <platform>" gets the value of each run, in the script it is {platform}. The runs are named after the test plus the values, e.g. Relative paths [platform=unix32, lang=C++], and each one gets its own line in the report. The editor shows how many runs the Examples give.

    Test projects

    A test project is a directory of files that tests use: source files, a .cppcheck project file, a .cfg file. They live in tests/projects/NAME and are edited under Test projects in the left column. Before every run the project is copied to a new empty directory, so a test always starts with the same files, even if an earlier test changed them. In a script the copy is {project:NAME}, or {project} when the test has only one project.

    @@ -186,9 +204,9 @@

    Scripts

    A test runs only when it has an up-to-date script. Generate it with Claude once, check it, and from then on it is replayed without AI.

    Generating a script

    -

    Open the test and press Generate script. gen.py starts the application with the test projects in place, and Claude does the test by hand: it looks at the accessibility tree, clicks and types, and checks the expected result. It then answers with a script. The script is replayed from scratch and saved in the test file only if that replay passes. When the replay fails, Claude is told why and tries again, 3 attempts in all. While a script is generated the editor is locked.

    +

    Open the test and press Generate script. gen.py starts the application with the test projects in place, and Claude does the test by hand: it looks at the accessibility tree, clicks and types, and checks the expected result (the Then steps). It then answers with a script. The script is replayed from scratch and saved in FILE.scripts.json only if that replay passes. When the replay fails, Claude is told why and tries again, 3 attempts in all. While a script is generated the editor is locked.

      -
    • A matrix test is explored with the first combination. The script uses {axis} where the values belong.
    • +
    • A Scenario Outline is explored with the first row of its Examples. The script uses {column} where the values belong.
    • When the application does not do what the expected result says, Claude still writes the script that checks it and marks that one check "observed_fail": true. The script is saved, and the test fails until the bug is fixed. That is a finding, not a broken script.
    • When the test can't be done through the GUI, Claude answers CANNOT: <reason> and no script is saved.
    @@ -218,10 +236,10 @@

    The script format

    expect: alive, exitedThe application is still running, or has quit by itself with status 0 expect: captureSearch regex in an element and save group 1 as a name; {name} in the later steps is replaced by it, for text that changes such as the version -

    Checks and actions wait for their element up to timeout seconds, 5 by default. The placeholders {work}, {home}, {project:NAME}, {axis} and {edition} are replaced before the run, captured values when the capture step has passed. The full description is under How to write a script below the script in the editor.

    +

    Checks and actions wait for their element up to timeout seconds, 5 by default. The placeholders {work}, {home}, {project:NAME}, {column} (of the Examples) and {edition} are replaced before the run, captured values when the capture step has passed. The full description is under How to write a script below the script in the editor.

    Outdated scripts

    -

    A script is made for the test's steps, expected result, TODO, matrix and the files of its test projects. When any of those change, the script is OUTDATED: it is kept but skipped by every run, and the editor says so in red. You then have three choices:

    +

    A script is made for the test's steps, the Background of the file, TODO, Examples and the files of its test projects. When any of those change, the script is OUTDATED: it is kept but skipped by every run, and the editor says so in red. You then have three choices:

    1. Generate script again.
    2. Approve the script when the change does not affect it, e.g. a typo fix in a step.
    3. @@ -234,14 +252,14 @@

      Running tests

      From the web interface

      Pick the edition in the header first: opensource, premium or both. Then:

        -
      • Run test in an open test runs that test, all its matrix combinations.
      • +
      • Run test in an open test runs that test, all the rows of its Examples.
      • ▶ next to a test file runs every test in that file.
      • Run all runs every test in every file.

      With "both", the run is done for one edition and then the other, and each edition gets its own report. The job box shows the test and step that is running and the results as they come. Stop ends the run at once: the test that was running is SKIP "aborted by user", and the report is written with the tests that are done. Only one job runs at a time.

      From the command line

      -
      ./run.py ~/cppcheck/build/bin/cppcheck-gui tests/application.json tests/projects.json --edition opensource
      +
      ./run.py ~/cppcheck/build/bin/cppcheck-gui tests/application.feature tests/projects.feature --edition opensource
      @@ -249,11 +267,11 @@

      From the command line

      - +
      OptionMeaning
      FILE ...One or more test files
      --edition opensource|premiumAPP is this edition; only the tests for it run. Without it all tests run and there is no history per edition
      --test TEXTOnly tests whose name contains TEXT (case-insensitive)
      --id IDOnly the test with this id, all its matrix combinations
      --id IDOnly the test with this id, all the rows of its Examples
      --src DIRGit checkout for the SHA in the report; default the directory of APP
      --out DIRWhere the run directories go; default results
      -

      run.py prints one line per test and the path of the report, and exits with 1 when a test failed, so it can be used in a script or CI job. Scripts are generated the same way with ./gen.py APP FILE.json; it takes --edition, --test, --id, --force (also tests that have a script), --model and --attempts.

      +

      run.py prints one line per test and the path of the report, and exits with 1 when a test failed, so it can be used in a script or CI job. Scripts are generated the same way with ./gen.py APP FILE.feature; it takes --edition, --test, --id, --force (also tests that have a script), --model and --attempts.

      What happens in a run

        @@ -320,9 +338,9 @@

        Troubleshooting

        Reference

        The commands, with all their options. web.py, run.py, gen.py and guitest.py also print their help with --help.

        web.py [--opensource APP] [--premium APP] [--src DIR] [--dir tests] [--out results] [--port 8080]
        -run.py APP FILE.json... [--edition opensource|premium] [--test TEXT] [--id ID] [--src DIR] [--out results]
        -gen.py APP FILE.json [--edition opensource|premium] [--test TEXT] [--id ID] [--force] [--model M] [--attempts 3]
        -testfile.py convert IN.md OUT.json
        +run.py APP FILE.feature... [--edition opensource|premium] [--test TEXT] [--id ID] [--src DIR] [--out results] +gen.py APP FILE.feature [--edition opensource|premium] [--test TEXT] [--id ID] [--force] [--model M] [--attempts 3] +testfile.py convert IN.md|IN.json OUT.feature

        guitest.py

        guitest.py is what Claude uses to drive the GUI, and you can use it by hand to look at the GUI the way a script sees it. The session lives in $GUITEST_STATE (default /tmp/guitest-<uid>/state.json).

        diff --git a/gui/manualtest/gen.py b/gui/manualtest/gen.py index 28a52264fe4..0a3360065dd 100755 --- a/gui/manualtest/gen.py +++ b/gui/manualtest/gen.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 """Let Claude write the scripts of the tests. The scripts are replayed by run.py without AI. - gen.py APP FILE.json [--edition opensource|premium] [--test SUBSTRING] [--force] [--model M] + gen.py APP FILE.feature [--edition opensource|premium] [--test SUBSTRING] [--force] [--model M] -For every test without a script (or every test with --force) Claude gets the steps, drives the +For every test without a script (or every test with --force) Claude gets the scenario, drives the GUI once with the guitest.py commands and answers with a script, see testfile.SCRIPT_DOC. -The script is run again from scratch and is saved in FILE.json only if that replay passes. -Matrix tests are explored with the first combination, the script uses {axis} where the -values of the matrix belong. With --edition APP is that edition, tests that are not for it are +The script is run again from scratch and is saved in FILE.scripts.json only if that replay passes. +A Scenario Outline is explored with the first row of its Examples, the script uses {column} where +the values of the row belong. With --edition APP is that edition, tests that are not for it are left out; the script is used for all the editions of the test. """ @@ -48,16 +48,19 @@ Test name: {name} +The test is written in Gherkin: the Background and the Given steps are the starting point, the When steps are +what you do and the Then steps are the EXPECTED result. + {body} -When you have done the test and checked the EXPECTED result, answer with a script that does the +When you have done the test and checked the EXPECTED result (the Then steps), answer with a script that does the same without you. The script is replayed by a program, so: - use the exact roles and names that you saw. Give a name and a role. Use "path" only when nothing else tells the element apart, paths change when the dialogs change (a file dialog's name field is found by its label) - a name matches any element that contains it, so always give the role too: a toolbar button "Preferences" and the dialog "Preferences" have the same name, the dialog is role "dialog" - give every step a "label" that says what it is for, in the words of the test -- end with "expect" steps that check the EXPECTED result, one for each thing that has to be true +- end with "expect" steps that check the EXPECTED result, at least one for each Then step - the files of the test are the test projects, refer to them with {{project:NAME}} paths; files that you made yourself are created by "write" steps (in {{work}}), not by you - the application is started fresh before the script, with an empty HOME @@ -101,14 +104,14 @@ def start_session(app, root, projects): return work, home, env, sub, variables -def explore(app, root, test, combo, edition, model, previous=''): +def explore(app, root, test, combo, edition, model, background, previous=''): """Claude does the test by hand and returns (script, error); with a script the error is (OBSERVED, reason).""" work, home, env, sub, variables = start_session(app, root, test.get('projects', [])) try: - note = ('this is a matrix test, you explore it with %s. Write {%s} in the script wherever that value ' - 'is used, the script is run once for every combination' % ( + note = ('this is a Scenario Outline, you explore it with the Examples row %s. Write {%s} in the script wherever ' + 'that value is used, the script is run once for every row of the Examples' % ( ', '.join('%s=%s' % kv for kv in combo.items()), '}, {'.join(combo)) - ) if combo else 'there is no matrix, write the values in the script as they are' + ) if combo else 'there are no Examples, write the values in the script as they are' others = [e for e in testfile.editions(test) if e != edition] if not edition: editionnote = 'there is one edition of the application' @@ -121,7 +124,7 @@ def explore(app, root, test, combo, edition, model, previous=''): ', '.join(sorted(testfile.project_files(root, n)))) for n in test.get('projects', [])) or ' none' prompt = PROMPT.format(guitest=GUITEST, help=sub('--help', capture_output=True).stdout, work=work, - name=test['name'], body=testfile.body(test, combo, edition), matrixnote=note, + name=test['name'], body=testfile.body(test, combo, edition, background), matrixnote=note, editionnote=editionnote, scriptdoc=testfile.SCRIPT_DOC, previous=previous, projects=projects) cmd = ['claude', '-p', '--allowedTools', 'Bash(python3 %s:*)' % GUITEST, 'Read', 'Write', '--add-dir', work] @@ -175,8 +178,8 @@ def main(): ap.add_argument('--attempts', type=int, default=3, help='tries per test, a failed replay is told to Claude') args = ap.parse_args() signal.signal(signal.SIGINT, signal.default_int_handler) # also when started in the background - if not args.file.endswith('.json'): - sys.exit('error: FILE must be a .json test file, convert markdown with testfile.py convert') + if not args.file.endswith(testfile.EXT): + sys.exit('error: FILE must be a %s test file, convert markdown or JSON with testfile.py convert' % testfile.EXT) app = os.path.abspath(args.app) doc = testfile.ensure_ids(testfile.load(args.file)) root = os.path.dirname(os.path.abspath(args.file)) @@ -195,11 +198,11 @@ def main(): continue combo = testfile.combinations(t)[0] values = dict(combo, edition=args.edition) if args.edition else combo - made_for = testfile.definition_hash(t, root) # the test may be edited while Claude works + made_for = testfile.definition_hash(t, root, doc.get('background')) # the test may be edited while Claude works print('%s: exploring...' % t['name'], flush=True) previous, saved = '', False for attempt in range(1, args.attempts + 1): - script, err = explore(app, root, t, combo, args.edition, args.model, previous) + script, err = explore(app, root, t, combo, args.edition, args.model, doc.get('background'), previous) if script is not None: # the replay uses the first combination, as the exploration did observed, why = err diff --git a/gui/manualtest/run.py b/gui/manualtest/run.py index e8c97111374..657cd6ce0a2 100755 --- a/gui/manualtest/run.py +++ b/gui/manualtest/run.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """Run the manual tests in a test file by letting Claude drive the GUI. - run.py APP FILE.json|FILE.md [--edition opensource|premium] [--test SUBSTRING] [--id ID] [--src DIR] [--out DIR] + run.py APP FILE.feature... [--edition opensource|premium] [--test SUBSTRING] [--id ID] [--src DIR] [--out DIR] -FILE.json is edited with web.py and can have test matrices, see testfile.py. FILE.md has -sections "## Test: NAME". For each test (and matrix combination) the application is started +FILE.feature is a Gherkin test file, edited with web.py or by hand, see testfile.py. The scripts +are in FILE.scripts.json. For each scenario (and each row of its Examples) the application is started fresh (empty HOME) and the script of the test is run with guitest.py commands, see runner.py. No AI is involved. With --edition APP is that edition and only the tests for it are run. Tests without a script are SKIP. A step that fails gives PASS -> FAIL with the @@ -166,10 +166,10 @@ def load_history(out, edition, current): def main(): ap = argparse.ArgumentParser() ap.add_argument('app') - ap.add_argument('file', nargs='+', help='one or more .json test files') + ap.add_argument('file', nargs='+', help='one or more .feature test files') ap.add_argument('--edition', choices=testfile.EDITIONS, help='the edition that APP is, only the tests for it') ap.add_argument('--test', help='only tests whose name contains this') - ap.add_argument('--id', help='only the test with this id (all its matrix combinations)') + ap.add_argument('--id', help='only the test with this id (all the rows of its Examples)') ap.add_argument('--src', help='git checkout of the application (default: the directory of APP)') ap.add_argument('--out', default=os.path.join(HERE, 'results')) ap.add_argument('--events', action='store_true', help='progress lines for the web interface') diff --git a/gui/manualtest/testfile.py b/gui/manualtest/testfile.py index 71d5b42ac2c..a915e7960fa 100644 --- a/gui/manualtest/testfile.py +++ b/gui/manualtest/testfile.py @@ -1,43 +1,61 @@ #!/usr/bin/env python3 -"""Test files for run.py: JSON (structured, with test matrices) or the older markdown. - -JSON format: - - {"title": "Project file dialog", - "tests": [ - {"name": "Relative paths", - "ticket": "#14983", optional - "enabled": true, optional, default true - "steps": ["Open the project dialog", "Select platform {platform}"], - "expected": "Relative paths are used in the XML", - "todo": "", optional, "cannot be automated" etc - "matrix": {"platform": ["native", "unix32"], "lang": ["C", "C++"]} optional - "editions": ["premium"] optional, default all editions - }]} - -"editions" are the versions of the application that the test is for, a subset of EDITIONS. A run -is for one edition (run.py --edition, with the binary of that edition), the tests that are not for -it are left out. The script is the same for all editions, {edition} in steps/expected/todo/script -is the name of the edition of the run. +"""Test files for run.py, written in Gherkin (FILE.feature), with the scripts in FILE.scripts.json. + + Feature: Project file dialog + + Background: + Given the application is started with no project loaded + + @id:t1a2b3c4d @ticket:14983 @project:relative-paths + Scenario: Relative paths + TODO: cannot be automated optional, a description line that starts with TODO: + When I open the project dialog + And I select the platform + Then relative paths are used in the XML + + Examples: + | platform | lang | + | native | C | + | unix32 | C++ | + +Feature: is the title of the file. Background: steps are done before every scenario of the file. +A scenario has steps that start with Given, When, Then, And, But or *, at least one Then (the +expected result). A step can be followed by a doc string (lines between \"\"\") or a table (| a | b |). +Lines that start with # are comments, they are not kept when the file is saved by web.py. + +Tags of a scenario (all optional): + @id:ID identifies a test when it is renamed, added when it is missing + @ticket:NUMBER the ticket of the test (#NUMBER) + @project:NAME a test project, see below; one tag per project + @edition:NAME an edition that the test is for, one of EDITIONS; one tag per edition, none: all + @disabled the test is not run +Other tags are kept but not used. + +A run is for one edition (run.py --edition, with the binary of that edition), the tests that are +not for it are left out. The script is the same for all editions, {edition} in the steps and the +script is the name of the edition of the run. + +Examples (Scenario Outline) are the test matrix: the test is run once for every row of the table. + in the steps and {column} in the script are replaced by the value of the row. "script" is what run.py executes, see SCRIPT_DOC below. It is written by gen.py (an AI explores -the GUI once) or by hand, and is then replayed without any AI. +the GUI once) or by hand, and is then replayed without any AI. The scripts are kept in +FILE.scripts.json, {"ID": {"script": [...], "script_for": "..."}}, next to the feature file. -"projects": ["name", ...] are test projects, directories with files and code in DIR/projects/NAME, +@project:NAME are test projects, directories with files and code in DIR/projects/NAME, shared by all tests next to them. Before every run they are copied to an empty temporary directory, -so a test always starts with the same files: {project:NAME} in a script is the copy of NAME, and -{project} when the test has exactly one project. +so a test always starts with the same files: {project:NAME} in steps and scripts is the copy of NAME, +and {project} when the test has exactly one project. -A script is made for the steps, expected result, todo, matrix and test projects (the files in them) of a test. "script_for" is a hash -of those fields at the time, when they are changed afterwards the script is OUTDATED: it is kept -but not run until it is generated again, edited or approved. "id" identifies a test when it is -renamed. +A script is made for the steps (with the Background), todo, examples and test projects (the files in +them) of a test. "script_for" is a hash of those at the time, when they are changed afterwards the +script is OUTDATED: it is kept but not run until it is generated again, edited or approved. -A test with a matrix is run once for every combination of the axis values (the cartesian -product). {axis} in steps/expected/todo is replaced by the value; {text} that is not an axis -is left as it is. +In Python a test file is a dict {"title", "background": [lines], "tests": [test]}, a test is +{"id", "name", "steps": [lines], "todo", "ticket", "enabled", "projects", "editions", "examples": +[{column: value}], "script", "script_for"}. The lines of the steps are Gherkin without indentation. - testfile.py convert IN.md OUT.json import a markdown test file + testfile.py convert IN.md|IN.json OUT.feature import a markdown or an older JSON test file """ import hashlib @@ -52,13 +70,15 @@ PROJECT_RE = re.compile(r'^[\w-]+$') PROJECTS = 'projects' # test projects: one directory per project, next to the test files EDITIONS = ('opensource', 'premium') +EXT = '.feature' +SCRIPTS_EXT = '.scripts.json' SCRIPT_DOC = """A script is a list of steps. Every step is an object with "do" (an action) or "expect" (a check), and an optional "label" that says what the step is for (shown when it fails). Elements are found with name (substring of the accessible name/text, case-insensitive), role, path, nth, exact, like the guitest.py commands. {work} is a temporary directory for files, {home} the HOME of the application, {project:NAME} the copy of the test project NAME ({project} when the test has just one), -use those for the files of a test instead of write steps. {axis} values of the test matrix and {edition} +use those for the files of a test instead of write steps. {column} values of the Examples row and {edition} (opensource or premium, the same script is used for both) are replaced before the run. actions @@ -163,6 +183,245 @@ def validate_script(script, where='script'): return errors +STEP_RE = re.compile(r'^(Given|When|Then|And|But|\*)\s+\S') +SCENARIO_RE = re.compile(r'^(Scenario Outline|Scenario Template|Scenario|Example):\s*(.*)$') +TAG_RE = re.compile(r'^@[^\s@]+$') +DOCSTRING = ('"""', '```') + + +class ParseError(ValueError): + pass + + +def validate_steps(lines, where, need_then=True): + """Errors in the Gherkin lines of a scenario or background.""" + errors, fence, keyword = [], None, None + for n, line in enumerate(lines, 1): + s = line.strip() + if fence: + if s == fence: + fence = None + continue + if s.startswith(DOCSTRING) or s.startswith('|'): + if keyword is None: + errors.append('%s line %d: a doc string or table must come after a step' % (where, n)) + if s.startswith(DOCSTRING): + fence = s[:3] + continue + m = STEP_RE.match(s) + if not m: + errors.append('%s line %d: a step must start with Given, When, Then, And, But or *: %s' % (where, n, s)) + continue + if keyword is None and m.group(1) in ('And', 'But'): + errors.append('%s line %d: the first step can not start with %s' % (where, n, m.group(1))) + keyword = keyword if m.group(1) in ('And', 'But', '*') else m.group(1) + if fence: + errors.append(where + ': a doc string is not closed with ' + fence) + if need_then and not any(re.match(r'^Then\s', line.strip()) for line in lines): + errors.append(where + ': needs a Then step, the expected result') + return errors + + +def parse(text): + """A .feature file as a test file dict. Raises ParseError.""" + doc = {'title': '', 'tests': []} + tags, section, fence, indent, table = [], None, None, 0, None + lines = text.splitlines() + + def fail(n, msg): + raise ParseError('line %d: %s' % (n, msg)) + + for n, raw in enumerate(lines, 1): + s = raw.strip() + if fence: # inside a doc string, kept as it is + section['steps'].append(raw[indent:] if raw[:indent].strip() == '' else s) + if s == fence: + section['steps'][-1] = s + fence = None + continue + if not s or s.startswith('#'): + continue + if s.startswith('@'): + for tag in s.split('#')[0].split(): + if not TAG_RE.match(tag): + fail(n, 'bad tag ' + tag) + tags.append(tag[1:]) + continue + table = table if s.startswith('|') else None + if s.startswith('Feature:'): + if 'feature' in doc: + fail(n, 'only one Feature per file') + doc['feature'] = True + doc['title'] = s[len('Feature:'):].strip() + if tags: + doc['tags'], tags = tags, [] + section = doc + continue + if 'feature' not in doc: + fail(n, 'the file must start with Feature:') + if s.startswith('Background:'): + if doc['tests'] or 'background' in doc: + fail(n, 'the Background must come before the scenarios, once') + doc['background'] = [] + section = {'steps': doc['background']} + continue + m = SCENARIO_RE.match(s) + if m: + section = scenario(m.group(2).strip(), tags) + doc['tests'].append(section) + tags = [] + continue + if s.startswith(('Rule:', 'Scenarios:')) or (s.startswith('Examples:') and section is doc): + fail(n, s.split(':')[0] + ' is not supported here') + if s.startswith('Examples:'): + if 'name' not in section: + fail(n, 'Examples only belong to a scenario') + section['examples'] = section.get('examples', []) + table = [] + continue + if table is not None: # a row of Examples + cells = [c.strip() for c in s.strip('|').split('|')] + if not s.endswith('|') or len(s) < 2: + fail(n, 'a table row must start and end with |') + if not table: + table.append(cells) + section['_columns'] = cells + elif len(cells) != len(table[0]): + fail(n, 'the row has %d cells, the header has %d' % (len(cells), len(table[0]))) + else: + section['examples'].append(dict(zip(table[0], cells))) + continue + if section is doc: + doc.setdefault('description', []).append(s) + continue + if 'examples' in section: + fail(n, 'steps must come before the Examples') + if not section['steps'] and not STEP_RE.match(s) and 'name' in section: + if s.startswith('TODO:'): # description lines of a scenario + section['todo'] = (section.get('todo', '') + '\n' + s[5:].strip()).strip() + else: + section.setdefault('description', []).append(s) + continue + section['steps'].append(s) + if s.startswith(DOCSTRING): + fence, indent = s[:3], len(raw) - len(raw.lstrip()) + if fence: + fail(len(lines), 'the doc string is not closed') + if 'feature' not in doc: + fail(1, 'no Feature: in the file') + del doc['feature'] + for t in doc['tests']: + if 'examples' in t and not t['examples']: + raise ParseError('%s: Examples without rows' % t['name']) + t.pop('_columns', None) + return doc + + +def scenario(name, tags): + t = {'name': name, 'steps': []} + other = [] + for tag in tags: + key, _, value = tag.partition(':') + if key == 'id' and value: + t['id'] = value + elif key == 'ticket' and value: + t['ticket'] = '#' + value if value.isdigit() else value + elif key == 'project' and value: + t.setdefault('projects', []).append(value) + elif key == 'edition' and value: + t.setdefault('editions', []).append(value) + elif tag == 'disabled': + t['enabled'] = False + else: + other.append(tag) + if other: + t['tags'] = other + return t + + +def write_steps(out, lines, indent): + """Doc strings and tables are indented below their step.""" + fence = None + for line in lines: + s = line.strip() + inner = fence or s.startswith(DOCSTRING) or s.startswith('|') + out.append((indent + ' ' * bool(inner) + line) if line else '') + if fence and s == fence: + fence = None + elif not fence and s.startswith(DOCSTRING): + fence = s[:3] + + +def table(rows): + cols = list(rows[0]) + width = {c: max(len(c), *(len(r.get(c, '')) for r in rows)) for c in cols} + fmt = lambda cells: '| ' + ' | '.join(v.ljust(width[c]) for c, v in zip(cols, cells)) + ' |' + return [fmt(cols)] + [fmt([r.get(c, '') for c in cols]) for r in rows] + + +def to_gherkin(doc): + out = [] + if doc.get('tags'): + out.append(' '.join('@' + t for t in doc['tags'])) + out.append('Feature: ' + doc.get('title', '')) + for line in doc.get('description', []): + out.append(' ' + line) + if doc.get('background'): + out += ['', ' Background:'] + write_steps(out, doc['background'], ' ') + for t in doc['tests']: + tags = ['id:' + t['id']] if t.get('id') else [] + if t.get('ticket'): + tags.append('ticket:' + t['ticket'].lstrip('#')) + tags += ['project:' + p for p in t.get('projects', [])] + if t.get('editions') and len(t['editions']) < len(EDITIONS): + tags += ['edition:' + e for e in t['editions']] + if t.get('enabled') is False: + tags.append('disabled') + tags += t.get('tags', []) + out.append('') + if tags: + out.append(' ' + ' '.join('@' + x for x in tags)) + out.append(' %s: %s' % ('Scenario Outline' if t.get('examples') else 'Scenario', t['name'])) + for line in t.get('description', []): + out.append(' ' + line) + for line in (t.get('todo') or '').splitlines(): + out.append(' TODO: ' + line.strip()) + write_steps(out, t.get('steps', []), ' ') + if t.get('examples'): + out += ['', ' Examples:'] + out += [' ' + row for row in table(t['examples'])] + return '\n'.join(out) + '\n' + + +def legacy_steps(steps, expected): + """Steps and expected result in plain words (markdown and older JSON files) as Gherkin lines.""" + lines = [] + for s in steps: + if s.startswith(('* ', '- ')) and lines: + lines[-1] += ' ' + s[2:] + else: + lines.append(('Given ' if not lines else 'When ' if len(lines) == 1 else 'And ') + s) + if expected: + lines.append('Then ' + ' '.join(expected.split())) + return lines + + +def from_legacy_json(old): + """An older JSON test file: steps, expected and a matrix of axes.""" + doc = {'title': old.get('title', ''), 'tests': []} + for o in old.get('tests', []): + t = {k: o[k] for k in ('id', 'name', 'ticket', 'enabled', 'todo', 'projects', 'editions', 'script', 'script_for') + if k in o} + matrix = o.get('matrix') or {} + steps = legacy_steps(o.get('steps', []), o.get('expected', '')) + t['steps'] = [re.sub(r'\{(%s)\}' % '|'.join(map(re.escape, matrix)), r'<\1>', s) if matrix else s for s in steps] + if matrix: + t['examples'] = [dict(zip(matrix, v)) for v in itertools.product(*matrix.values())] + doc['tests'].append(t) + return doc + + def from_markdown(text): m = re.search(r'^#\s+(.+)$', text, re.M) doc = {'title': m.group(1).strip() if m else '', 'tests': []} @@ -186,6 +445,9 @@ def from_markdown(text): test[field] += '\n' + s else: test['steps'].append(re.sub(r'^\d+\.\s+', '', s)) + test['steps'] = legacy_steps(test['steps'], test.pop('expected')) + if not test['todo']: + del test['todo'] doc['tests'].append(test) return doc @@ -196,6 +458,13 @@ def validate(doc, root=None): errors = [] if not isinstance(doc, dict) or not isinstance(doc.get('tests'), list): return ['the document needs a "tests" list'] + if not isinstance(doc.get('title', ''), str) or '\n' in doc.get('title', ''): + errors.append('the title must be one line') + background = doc.get('background', []) + if not isinstance(background, list) or not all(isinstance(s, str) for s in background): + errors.append('the background must be a list of lines') + else: + errors += validate_steps(background, 'Background', need_then=False) names = set() for i, t in enumerate(doc['tests'], 1): where = 'test %d' % i @@ -205,15 +474,22 @@ def validate(doc, root=None): name = t.get('name') if not isinstance(name, str) or not name.strip(): errors.append(where + ': name is missing') + elif '\n' in name: + errors.append(where + ': the name must be one line') elif name in names: errors.append('%s: duplicate name "%s"' % (where, name)) else: names.add(name) - if not isinstance(t.get('steps', []), list) or not all(isinstance(s, str) for s in t.get('steps', [])): - errors.append(where + ': steps must be a list of strings') - for k in ('expected', 'todo', 'ticket'): + steps = t.get('steps', []) + if not isinstance(steps, list) or not all(isinstance(s, str) and '\n' not in s for s in steps): + errors.append(where + ': steps must be a list of lines') + else: + errors += validate_steps(steps, where) + for k in ('todo', 'ticket', 'id'): if not isinstance(t.get(k, ''), str): errors.append('%s: %s must be a string' % (where, k)) + if re.search(r'\s', t.get('ticket', '') or '') or re.search(r'\W', t.get('id', '') or ''): + errors.append(where + ': the ticket and the id can not have spaces, they are tags') projects = t.get('projects', []) if not isinstance(projects, list) or not all(isinstance(p, str) and PROJECT_RE.match(p) for p in projects): errors.append(where + ': projects must be a list of project names (letters, digits, _ and -)') @@ -225,29 +501,53 @@ def validate(doc, root=None): or not all(e in EDITIONS for e in editions)): errors.append('%s: editions must be a list of one or more of %s' % (where, ', '.join(EDITIONS))) errors += validate_script(t.get('script', []), where + ' script') - matrix = t.get('matrix', {}) - if not isinstance(matrix, dict): - errors.append(where + ': matrix must be an object') + examples = t.get('examples', []) + if not isinstance(examples, list) or not all(isinstance(r, dict) for r in examples): + errors.append(where + ': examples must be a list of rows') continue - for axis, values in matrix.items(): - if not AXIS_RE.match(axis): - errors.append('%s: axis name "%s" must be letters, digits and _' % (where, axis)) - if not isinstance(values, list) or not values or not all(isinstance(v, str) and v for v in values): - errors.append('%s: axis "%s" needs a list of non-empty values' % (where, axis)) + if examples: + cols = list(examples[0]) + for c in cols: + if not AXIS_RE.match(c): + errors.append('%s: Examples column "%s" must be letters, digits and _' % (where, c)) + if any(list(r) != cols for r in examples): + errors.append(where + ': every row of the Examples must have the same columns') + if not all(isinstance(v, str) and v and '|' not in v for r in examples for v in r.values()): + errors.append(where + ': every cell of the Examples needs a value without |') + if len({json.dumps(r, sort_keys=True) for r in examples}) != len(examples): + errors.append(where + ': a row of the Examples is there twice') return errors +def scripts_path(path): + return path[:-len(EXT)] + SCRIPTS_EXT if path.endswith(EXT) else path + SCRIPTS_EXT + + def load(path): with open(path, encoding='utf-8') as f: text = f.read() - return json.loads(text) if path.endswith('.json') else from_markdown(text) + if path.endswith('.md'): + return from_markdown(text) + if path.endswith('.json'): + return from_legacy_json(json.loads(text)) + doc = parse(text) + try: + with open(scripts_path(path), encoding='utf-8') as f: + scripts = json.load(f) + except FileNotFoundError: + scripts = {} + for t in doc['tests']: + s = scripts.get(t.get('id')) if t.get('id') else None + if isinstance(s, dict) and s.get('script'): + t['script'] = s['script'] + if s.get('script_for'): + t['script_for'] = s['script_for'] + return doc def combinations(test): - """The matrix of a test as a list of {axis: value}; [{}] when there is no matrix.""" - matrix = test.get('matrix') or {} - axes = list(matrix) - return [dict(zip(axes, values)) for values in itertools.product(*(matrix[a] for a in axes))] + """The rows of the Examples of a test as a list of {column: value}; [{}] when there are none.""" + return [dict(r) for r in test.get('examples') or []] or [{}] def editions(test): @@ -259,28 +559,29 @@ def substitute(text, combo): return re.sub(r'\{([\w:-]+)\}', lambda m: combo.get(m.group(1), m.group(0)), text) -def body(test, combo, edition=None): +def substitute_step(text, combo): + """ of the Examples and {edition} in a line of the steps.""" + text = re.sub(r'<(\w+)>', lambda m: combo.get(m.group(1), m.group(0)), text) + return substitute(text, {k: v for k, v in combo.items() if k == 'edition'}) + + +def body(test, combo, edition=None, background=()): + """The test as it is shown to Claude: the scenario in Gherkin, with the values of the run.""" parts = [] if test.get('ticket'): parts.append('Ticket: ' + test['ticket']) if edition: parts.append('Edition of the application: ' + edition) if combo: - parts.append('Matrix values for this run: ' + ', '.join('%s=%s' % kv for kv in combo.items())) + parts.append('Examples row for this run: ' + ', '.join('%s=%s' % kv for kv in combo.items())) combo = dict(combo, edition=edition) if edition else combo - if test.get('steps'): - lines, n = [], 0 - for s in test['steps']: - s = substitute(s, combo) - if s.startswith(('* ', '- ')): # a sub item of the step above - lines.append(' ' + s) - else: - n += 1 - lines.append('%d. %s' % (n, s)) - parts.append('Steps:\n' + '\n'.join(lines)) - parts.append('EXPECTED: ' + substitute(test.get('expected', ''), combo)) + lines = [] + if background: + lines += ['Background:'] + [' ' + substitute_step(s, combo) for s in background] + [''] + lines += ['Scenario: ' + test['name']] + [' ' + substitute_step(s, combo) for s in test.get('steps', [])] + parts.append('\n'.join(lines)) if test.get('todo'): - parts.append('TODO: ' + substitute(test['todo'], combo)) + parts.append('TODO: ' + substitute_step(test['todo'], combo)) return '\n\n'.join(parts) @@ -318,10 +619,11 @@ def materialize(root, names, work): return variables -def definition_hash(test, root=None): - """What a script depends on: changing any of these, or a file of a test project, makes the script outdated.""" - d = {k: test.get(k) or ([] if k == 'steps' else {} if k == 'matrix' else '') - for k in ('steps', 'expected', 'todo', 'matrix')} +def definition_hash(test, root=None, background=()): + """What a script depends on: changing any of these, the Background of the file or a file of a + test project makes the script outdated.""" + d = {'steps': list(background or []) + list(test.get('steps') or []), 'todo': test.get('todo') or '', + 'examples': test.get('examples') or []} if test.get('projects'): d['projects'] = {p: project_hash(root, p) if root and os.path.isdir(os.path.join(root, PROJECTS, p)) else 'missing' for p in test['projects']} @@ -335,16 +637,18 @@ def ensure_ids(doc): return doc -def is_stale(test, root=None): +def is_stale(test, root=None, background=()): """A script that was made for another version of the test. No script_for: old file, trusted.""" - return bool(test.get('script') and test.get('script_for') and test['script_for'] != definition_hash(test, root)) + return bool(test.get('script') and test.get('script_for') + and test['script_for'] != definition_hash(test, root, background)) def stamp_scripts(doc, old, root=None): """Before saving. A new, edited or approved ("_approve") script is current for the test as it is now. An untouched script keeps what it was made for, so editing the test makes it outdated.""" ensure_ids(doc) - prev = {t['id']: t for t in ensure_ids(old or {}).get('tests', [])} + old = ensure_ids(old or {}) + prev = {t['id']: t for t in old.get('tests', [])} for t in doc['tests']: t.pop('_stale', None) approve = t.pop('_approve', False) @@ -353,9 +657,9 @@ def stamp_scripts(doc, old, root=None): continue p = prev.get(t['id']) if approve or p is None or p.get('script') != t['script']: - t['script_for'] = definition_hash(t, root) + t['script_for'] = definition_hash(t, root, doc.get('background')) else: - t['script_for'] = p.get('script_for') or definition_hash(p, root) + t['script_for'] = p.get('script_for') or definition_hash(p, root, old.get('background')) return doc @@ -370,13 +674,14 @@ def subst_deep(obj, combo): def expand(path, edition=None): - """One dict {name, id, body, script, stale, combo} per matrix combination of every enabled test. + """One dict {name, id, body, script, stale, combo} per Examples row of every enabled test. With EDITION only the tests for that edition, and {edition} is replaced.""" doc = ensure_ids(load(path)) root = os.path.dirname(os.path.abspath(path)) errors = validate(doc, root) if errors: sys.exit('error: %s: %s' % (path, '; '.join(errors))) + background = doc.get('background', []) out = [] for t in doc['tests']: if not t.get('enabled', True) or edition and edition not in editions(t): @@ -384,21 +689,55 @@ def expand(path, edition=None): for combo in combinations(t): name = t['name'] + (' [%s]' % ', '.join('%s=%s' % kv for kv in combo.items()) if combo else '') values = dict(combo, edition=edition) if edition else combo - out.append({'name': name, 'id': t['id'], 'body': body(t, combo, edition), 'combo': combo, 'root': root, - 'projects': t.get('projects', []), 'stale': is_stale(t, root), + out.append({'name': name, 'id': t['id'], 'body': body(t, combo, edition, background), 'combo': combo, + 'root': root, 'projects': t.get('projects', []), 'stale': is_stale(t, root, background), 'script': subst_deep(t.get('script', []), values)}) return out -def save(path, doc): +def write_atomic(path, text): tmp = path + '.tmp' with open(tmp, 'w', encoding='utf-8') as f: - json.dump(doc, f, indent=2, ensure_ascii=False) - f.write('\n') + f.write(text) os.replace(tmp, path) +def save(path, doc): + """Write FILE.feature and the scripts in FILE.scripts.json.""" + if not path.endswith(EXT): + raise ValueError('a test file is saved as ' + EXT) + ensure_ids(doc) + scripts = {t['id']: {k: t[k] for k in ('script', 'script_for') if t.get(k)} for t in doc['tests'] if t.get('script')} + write_atomic(path, to_gherkin(doc)) + if scripts: + write_atomic(scripts_path(path), json.dumps(scripts, indent=2, ensure_ascii=False) + '\n') + elif os.path.exists(scripts_path(path)): + os.remove(scripts_path(path)) + + +def convert(src, dst): + """Import a markdown or an older JSON test file. Scripts that were up to date stay up to date.""" + root = os.path.dirname(os.path.abspath(src)) + current = set() + if src.endswith('.json'): + with open(src, encoding='utf-8') as f: + old = json.load(f) + for o in old.get('tests', []): + d = {k: o.get(k) or ([] if k == 'steps' else {} if k == 'matrix' else '') for k in ('steps', 'expected', 'todo', 'matrix')} + if o.get('projects'): + d['projects'] = {p: project_hash(root, p) if os.path.isdir(os.path.join(root, PROJECTS, p)) else 'missing' + for p in o['projects']} + h = hashlib.sha1(json.dumps(d, sort_keys=True, ensure_ascii=False).encode()).hexdigest()[:12] + if o.get('script') and o.get('script_for') in (None, h): + current.add(o.get('id')) + doc = ensure_ids(load(src)) + for t in doc['tests']: + if t['id'] in current: + t['script_for'] = definition_hash(t, os.path.dirname(os.path.abspath(dst)), doc.get('background')) + save(dst, doc) + + if __name__ == '__main__': - if len(sys.argv) != 4 or sys.argv[1] != 'convert': + if len(sys.argv) != 4 or sys.argv[1] != 'convert' or not sys.argv[3].endswith(EXT): sys.exit(__doc__) - save(sys.argv[3], load(sys.argv[2])) + convert(sys.argv[2], sys.argv[3]) diff --git a/gui/manualtest/tests/analysis.feature b/gui/manualtest/tests/analysis.feature new file mode 100644 index 00000000000..fc43506dd98 --- /dev/null +++ b/gui/manualtest/tests/analysis.feature @@ -0,0 +1,55 @@ +Feature: Analysis and recheck + + Background: + Given the application is started with no project loaded + + @id:te65b1fa8 @project:basic-project + Scenario: Reanalyze all files + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I change {project:basic-project}/src/a.c and {project:basic-project}/src/b.c so that they only contain ";" + And I click Reanalyze all files + And I wait until the analysis is done + Then there are no results for a.c + And there are no results for b.c + + @id:tc707b85b @ticket:14893 @project:recheck + Scenario: Recheck a changed file + Given the project file {project:recheck}/p.cppcheck is opened + And the analysis is done + And the results show a nullPointer error in a.c and an arrayIndexOutOfBounds error in b.c + When I copy {project:recheck}/a_v2.c over {project:recheck}/a.c (a_v2.c has an uninitvar error instead) + And I right click on the file a.c in the results and click Recheck + Then the results for a.c are refreshed + And the nullPointer error in a.c is gone + And the uninitvar error from the changed a.c is shown + And the arrayIndexOutOfBounds error in b.c is still shown, unchanged + + @id:tc550387d @project:basic-project + Scenario: Recheck a header file + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I right click on the file util.h in the results + Then the context menu of util.h has a Recheck entry + When I click Recheck + And I wait until the analysis is done + Then the analysis is run again for the files that include util.h + And the nullPointer error in util.h is shown again + + @id:ta6f3b40b @project:syntax-error + Scenario Outline: Analyze a broken source file + Given the project file {project:syntax-error}/.cppcheck checks .c, a broken or unusual source file, and ok.c, which has a nullPointer error + When I open the project file {project:syntax-error}/.cppcheck + And I wait until the analysis is done + Then the analysis finishes + And the application does not crash + And the nullPointer error in ok.c is shown + + Examples: + | case | + | braces | + | empty | + | binary | + | latin1 | + | long_line | + | unclosed_nesting | diff --git a/gui/manualtest/tests/analysis.json b/gui/manualtest/tests/analysis.json deleted file mode 100644 index 4277443d5cf..00000000000 --- a/gui/manualtest/tests/analysis.json +++ /dev/null @@ -1,457 +0,0 @@ -{ - "title": "Analysis and recheck", - "tests": [ - { - "id": "te65b1fa8", - "name": "Reanalyze all files", - "steps": [ - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Edit the files {project:basic-project}/src/a.c and {project:basic-project}/src/b.c so they only contain \";\".", - "Click Reanalyze all files and wait until the analysis is done." - ], - "expected": "When the analysis is done there should be no results for a.c and b.c", - "script": [ - { - "label": "Open the File menu", - "do": "click", - "name": "File", - "role": "menu item" - }, - { - "label": "Choose Open Project File", - "do": "click", - "name": "Open Project File", - "role": "menu item" - }, - { - "label": "Enter the project file path", - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck" - }, - { - "label": "Confirm the file dialog", - "do": "key", - "keys": [ - "Return" - ] - }, - { - "label": "Wait until the first analysis shows results for a.c", - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "timeout": 30 - }, - { - "label": "Results for b.c are shown too", - "expect": "visible", - "name": "src/b.c", - "role": "table cell" - }, - { - "label": "Edit a.c so it only contains ;", - "do": "write", - "path": "{project:basic-project}/src/a.c", - "content": ";" - }, - { - "label": "Edit b.c so it only contains ;", - "do": "write", - "path": "{project:basic-project}/src/b.c", - "content": ";" - }, - { - "label": "Click Reanalyze all files", - "do": "click", - "name": "Reanalyze all files", - "role": "button" - }, - { - "label": "Wait until the analysis is done (No errors found dialog)", - "expect": "visible", - "name": "No errors found", - "role": "label", - "timeout": 30 - }, - { - "label": "Close the dialog", - "do": "click", - "name": "OK", - "role": "button" - }, - { - "label": "No results for a.c", - "expect": "gone", - "name": "src/a.c", - "role": "table cell" - }, - { - "label": "No results for b.c", - "expect": "gone", - "name": "src/b.c", - "role": "table cell" - } - ], - "projects": [ - "basic-project" - ], - "script_for": "0198ba992317" - }, - { - "id": "tc707b85b", - "name": "Recheck a changed file", - "ticket": "#14893", - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:recheck}/p.cppcheck and wait until the analysis is done. The results show a nullPointer error in a.c and an arrayIndexOutOfBounds error in b.c.", - "Change a.c: copy {project:recheck}/a_v2.c over {project:recheck}/a.c (a_v2.c has an uninitvar error instead).", - "Right click on the file a.c in the results and click Recheck." - ], - "expected": "The results for a.c are refreshed: the nullPointer error is gone and the uninitvar error from the changed a.c is shown. The results for b.c are still shown, unchanged (arrayIndexOutOfBounds in b.c).", - "script": [ - { - "do": "wait", - "name": "Cppcheck", - "role": "frame", - "timeout": 20, - "label": "Application started with no project" - }, - { - "do": "click", - "name": "Open Project File", - "role": "button", - "label": "Open the project file dialog" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:recheck}/p.cppcheck", - "label": "Enter the path of p.cppcheck" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Open the project" - }, - { - "expect": "visible", - "name": "b.c", - "role": "table cell", - "timeout": 30, - "label": "Analysis done, b.c in the results" - }, - { - "expect": "visible", - "name": "a.c", - "role": "table cell", - "timeout": 30, - "label": "a.c in the results" - }, - { - "do": "sleep", - "seconds": 3, - "label": "Let the analysis finish" - }, - { - "do": "copy", - "from": "{project:recheck}/a_v2.c", - "to": "{project:recheck}/a.c", - "label": "Change a.c by copying a_v2.c over it" - }, - { - "do": "rightclick", - "name": "a.c", - "role": "table cell", - "nth": 0, - "label": "Right click on a.c in the results" - }, - { - "expect": "ocr", - "contains": "Recheck", - "label": "Context menu shows Recheck" - }, - { - "do": "key", - "keys": [ - "Down", - "Return" - ], - "label": "Click Recheck" - }, - { - "do": "sleep", - "seconds": 4, - "label": "Wait for the recheck" - }, - { - "do": "click", - "name": "b.c", - "role": "table cell", - "nth": 0, - "label": "Select the b.c row" - }, - { - "do": "key", - "keys": [ - "Right", - "Down", - "Down", - "Down", - "Right" - ], - "label": "Expand b.c and a.c to show their results" - }, - { - "expect": "text", - "contains": "uninitvar", - "label": "The uninitvar error of the changed a.c is shown" - }, - { - "expect": "text", - "not_contains": "nullPointer", - "label": "The nullPointer error is gone" - }, - { - "expect": "text", - "contains": "arrayIndexOutOfBounds", - "label": "The arrayIndexOutOfBounds error in b.c is still shown" - } - ], - "projects": [ - "recheck" - ], - "script_for": "07cf713caa82" - }, - { - "id": "tc550387d", - "name": "Recheck a header file", - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Right click on the file util.h in the results and click Recheck. Wait until the analysis is done." - ], - "expected": "The context menu of util.h has a Recheck entry. The analysis is run again for the files that include util.h and the nullPointer error in util.h is shown again afterwards.", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the file dialog" - }, - { - "expect": "visible", - "name": "include/util.h", - "role": "table cell", - "timeout": 30, - "label": "Analysis is done and util.h is in the results" - }, - { - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "timeout": 30, - "label": "Analysis finished for all files" - }, - { - "do": "rightclick", - "name": "include/util.h", - "role": "table cell", - "label": "Right click on util.h in the results" - }, - { - "do": "sleep", - "seconds": 1, - "label": "Wait for the context menu" - }, - { - "do": "key", - "keys": [ - "Down", - "Return" - ], - "label": "Click the Recheck entry of the context menu" - }, - { - "expect": "text", - "contains": "Checking /tmp", - "timeout": 30, - "label": "The analysis was run again" - }, - { - "expect": "text", - "contains": "src/a.c ...", - "timeout": 30, - "label": "The file that includes util.h is checked again" - }, - { - "do": "sleep", - "seconds": 3, - "label": "Wait until the analysis is done" - }, - { - "do": "click", - "name": "include/util.h", - "role": "table cell", - "mouse": true, - "label": "Select util.h with the mouse so it has focus" - }, - { - "do": "key", - "keys": [ - "Right" - ], - "label": "Expand util.h" - }, - { - "expect": "visible", - "name": "nullPointer", - "role": "table cell", - "timeout": 10, - "label": "The nullPointer error in util.h is shown again" - }, - { - "expect": "visible", - "name": "Null pointer dereference", - "role": "table cell", - "timeout": 5, - "label": "The nullPointer message is shown" - } - ], - "projects": [ - "basic-project" - ], - "script_for": "e29e38750a0d" - }, - { - "id": "ta6f3b40b", - "name": "Analyze a broken source file", - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:syntax-error}/{case}.cppcheck and wait until the analysis is done. It checks {case}.c, a broken or unusual source file, and ok.c, which has a nullPointer error." - ], - "expected": "The analysis finishes and the application does not crash. The nullPointer error in ok.c is shown.", - "script": [ - { - "label": "Start the application with no project loaded", - "expect": "visible", - "name": "Cppcheck", - "role": "frame" - }, - { - "label": "Open the project file", - "do": "click", - "name": "Open Project File", - "role": "button" - }, - { - "label": "Wait for the Select Project File dialog", - "do": "wait", - "name": "Select Project File", - "timeout": 10 - }, - { - "label": "Enter the project file {case}.cppcheck", - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:syntax-error}/{case}.cppcheck" - }, - { - "label": "Confirm the file dialog", - "do": "key", - "keys": [ - "Return" - ] - }, - { - "label": "The project is loaded", - "expect": "visible", - "name": "Project: {case}.cppcheck", - "role": "frame", - "timeout": 15 - }, - { - "label": "Wait until the analysis is done (ok.c is checked)", - "expect": "text", - "contains": "Checking ok.c", - "timeout": 60 - }, - { - "label": "Give the analysis time to finish", - "do": "sleep", - "seconds": 3 - }, - { - "label": "The analysis finishes: Stop analysis is disabled again", - "expect": "state", - "name": "Stop analysis", - "role": "button", - "not_state": "enabled", - "timeout": 60 - }, - { - "label": "The analysis finishes: Reanalyze all files is enabled again", - "expect": "state", - "name": "Reanalyze all files", - "role": "button", - "state": "enabled", - "timeout": 30 - }, - { - "label": "The broken source file {case}.c was analyzed", - "expect": "text", - "contains": "Checking {case}.c" - }, - { - "label": "The nullPointer error in ok.c is shown (ok.c has a result row)", - "expect": "visible", - "name": "ok.c", - "role": "table cell" - }, - { - "label": "The application does not crash", - "expect": "alive" - } - ], - "projects": [ - "syntax-error" - ], - "matrix": { - "case": [ - "braces", - "empty", - "binary", - "latin1", - "long_line", - "unclosed_nesting" - ] - }, - "script_for": "ed80d3198afb" - } - ] -} diff --git a/gui/manualtest/tests/analysis.scripts.json b/gui/manualtest/tests/analysis.scripts.json new file mode 100644 index 00000000000..54d89a4c39d --- /dev/null +++ b/gui/manualtest/tests/analysis.scripts.json @@ -0,0 +1,399 @@ +{ + "te65b1fa8": { + "script": [ + { + "label": "Open the File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Choose Open Project File", + "do": "click", + "name": "Open Project File", + "role": "menu item" + }, + { + "label": "Enter the project file path", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck" + }, + { + "label": "Confirm the file dialog", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "Wait until the first analysis shows results for a.c", + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30 + }, + { + "label": "Results for b.c are shown too", + "expect": "visible", + "name": "src/b.c", + "role": "table cell" + }, + { + "label": "Edit a.c so it only contains ;", + "do": "write", + "path": "{project:basic-project}/src/a.c", + "content": ";" + }, + { + "label": "Edit b.c so it only contains ;", + "do": "write", + "path": "{project:basic-project}/src/b.c", + "content": ";" + }, + { + "label": "Click Reanalyze all files", + "do": "click", + "name": "Reanalyze all files", + "role": "button" + }, + { + "label": "Wait until the analysis is done (No errors found dialog)", + "expect": "visible", + "name": "No errors found", + "role": "label", + "timeout": 30 + }, + { + "label": "Close the dialog", + "do": "click", + "name": "OK", + "role": "button" + }, + { + "label": "No results for a.c", + "expect": "gone", + "name": "src/a.c", + "role": "table cell" + }, + { + "label": "No results for b.c", + "expect": "gone", + "name": "src/b.c", + "role": "table cell" + } + ], + "script_for": "c692e526a6cd" + }, + "tc707b85b": { + "script": [ + { + "do": "wait", + "name": "Cppcheck", + "role": "frame", + "timeout": 20, + "label": "Application started with no project" + }, + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the project file dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:recheck}/p.cppcheck", + "label": "Enter the path of p.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project" + }, + { + "expect": "visible", + "name": "b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis done, b.c in the results" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 30, + "label": "a.c in the results" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the analysis finish" + }, + { + "do": "copy", + "from": "{project:recheck}/a_v2.c", + "to": "{project:recheck}/a.c", + "label": "Change a.c by copying a_v2.c over it" + }, + { + "do": "rightclick", + "name": "a.c", + "role": "table cell", + "nth": 0, + "label": "Right click on a.c in the results" + }, + { + "expect": "ocr", + "contains": "Recheck", + "label": "Context menu shows Recheck" + }, + { + "do": "key", + "keys": [ + "Down", + "Return" + ], + "label": "Click Recheck" + }, + { + "do": "sleep", + "seconds": 4, + "label": "Wait for the recheck" + }, + { + "do": "click", + "name": "b.c", + "role": "table cell", + "nth": 0, + "label": "Select the b.c row" + }, + { + "do": "key", + "keys": [ + "Right", + "Down", + "Down", + "Down", + "Right" + ], + "label": "Expand b.c and a.c to show their results" + }, + { + "expect": "text", + "contains": "uninitvar", + "label": "The uninitvar error of the changed a.c is shown" + }, + { + "expect": "text", + "not_contains": "nullPointer", + "label": "The nullPointer error is gone" + }, + { + "expect": "text", + "contains": "arrayIndexOutOfBounds", + "label": "The arrayIndexOutOfBounds error in b.c is still shown" + } + ], + "script_for": "0c160519508a" + }, + "tc550387d": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and util.h is in the results" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis finished for all files" + }, + { + "do": "rightclick", + "name": "include/util.h", + "role": "table cell", + "label": "Right click on util.h in the results" + }, + { + "do": "sleep", + "seconds": 1, + "label": "Wait for the context menu" + }, + { + "do": "key", + "keys": [ + "Down", + "Return" + ], + "label": "Click the Recheck entry of the context menu" + }, + { + "expect": "text", + "contains": "Checking /tmp", + "timeout": 30, + "label": "The analysis was run again" + }, + { + "expect": "text", + "contains": "src/a.c ...", + "timeout": 30, + "label": "The file that includes util.h is checked again" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Wait until the analysis is done" + }, + { + "do": "click", + "name": "include/util.h", + "role": "table cell", + "mouse": true, + "label": "Select util.h with the mouse so it has focus" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand util.h" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "timeout": 10, + "label": "The nullPointer error in util.h is shown again" + }, + { + "expect": "visible", + "name": "Null pointer dereference", + "role": "table cell", + "timeout": 5, + "label": "The nullPointer message is shown" + } + ], + "script_for": "dc8fcd30e236" + }, + "ta6f3b40b": { + "script": [ + { + "label": "Start the application with no project loaded", + "expect": "visible", + "name": "Cppcheck", + "role": "frame" + }, + { + "label": "Open the project file", + "do": "click", + "name": "Open Project File", + "role": "button" + }, + { + "label": "Wait for the Select Project File dialog", + "do": "wait", + "name": "Select Project File", + "timeout": 10 + }, + { + "label": "Enter the project file {case}.cppcheck", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:syntax-error}/{case}.cppcheck" + }, + { + "label": "Confirm the file dialog", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "The project is loaded", + "expect": "visible", + "name": "Project: {case}.cppcheck", + "role": "frame", + "timeout": 15 + }, + { + "label": "Wait until the analysis is done (ok.c is checked)", + "expect": "text", + "contains": "Checking ok.c", + "timeout": 60 + }, + { + "label": "Give the analysis time to finish", + "do": "sleep", + "seconds": 3 + }, + { + "label": "The analysis finishes: Stop analysis is disabled again", + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 60 + }, + { + "label": "The analysis finishes: Reanalyze all files is enabled again", + "expect": "state", + "name": "Reanalyze all files", + "role": "button", + "state": "enabled", + "timeout": 30 + }, + { + "label": "The broken source file {case}.c was analyzed", + "expect": "text", + "contains": "Checking {case}.c" + }, + { + "label": "The nullPointer error in ok.c is shown (ok.c has a result row)", + "expect": "visible", + "name": "ok.c", + "role": "table cell" + }, + { + "label": "The application does not crash", + "expect": "alive" + } + ], + "script_for": "3090dfe1cf47" + } +} diff --git a/gui/manualtest/tests/application.feature b/gui/manualtest/tests/application.feature new file mode 100644 index 00000000000..378e1a636cc --- /dev/null +++ b/gui/manualtest/tests/application.feature @@ -0,0 +1,59 @@ +Feature: Application, menus and help + + Background: + Given the application is started with no project loaded + + @id:t41f9102b + Scenario: All menus open + When I open each of the menus File, Edit, View, Analyze and Help in turn + And I look at its entries and close it with Escape + Then each of the menus File, Edit, View, Analyze and Help opens + And each menu shows at least one entry + + @id:t1cc2be0f + Scenario: Show and hide toolbars + Given all entries of View > Toolbar are checked + When I uncheck the entries of View > Toolbar one at a time + Then a toolbar that is unchecked is no longer shown in the main window + And the toolbars that are still checked are shown + + @id:t5fdeee58 @edition:opensource + Scenario: Same version in the title and in About + Given the title of the main window is "Cppcheck " + When I note the version in the title + And I open Help > About + Then the About dialog shows the same version as the title of the main window + + @id:t90321d9c + Scenario: Help contents + When I open Help > Contents + Then a help window opens + And the help window has contents, it is not empty + + @id:tc059b730 + Scenario: Other Help menu entries + When I open each of the other entries of the Help menu in turn (About and the rest) + And I close each window with Escape or its Close button + Then every entry opens a dialog or window with text in it + And none of them is empty + + @id:t5bf8f6ae @ticket:13982 + Scenario: Scratch pad + When I open View > Scratch Pad + And I enter this code, one line each: + """ + int main(void) + { + int *p = 0; + return *p; + } + """ + And I click Check + Then the code is analysed + And a nullPointer error is shown + And the application does not crash + + @id:t1a2285d8 + Scenario: Quit from the File menu + When I open File > Quit + Then the application closes diff --git a/gui/manualtest/tests/application.json b/gui/manualtest/tests/application.json deleted file mode 100644 index 863ef4d26c4..00000000000 --- a/gui/manualtest/tests/application.json +++ /dev/null @@ -1,607 +0,0 @@ -{ - "title": "Application, menus and help", - "tests": [ - { - "id": "t41f9102b", - "name": "All menus open", - "steps": [ - "Start the application with no project loaded.", - "Click the File menu, look at its entries and close it with Escape. Do the same for the Edit, View, Analyze and Help menus." - ], - "expected": "Each of the menus File, Edit, View, Analyze and Help opens and shows at least one entry.", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "expect": "visible", - "name": "Open XML...", - "role": "menu item", - "label": "File menu shows entries" - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Close the File menu" - }, - { - "do": "click", - "name": "Edit", - "role": "menu item", - "label": "Open the Edit menu" - }, - { - "expect": "visible", - "name": "Clear results", - "role": "menu item", - "label": "Edit menu shows entries" - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Close the Edit menu" - }, - { - "do": "click", - "name": "View", - "role": "menu item", - "label": "Open the View menu" - }, - { - "expect": "visible", - "name": "Toolbars", - "role": "menu item", - "label": "View menu shows entries" - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Close the View menu" - }, - { - "do": "click", - "name": "Analyze", - "role": "menu item", - "label": "Open the Analyze menu" - }, - { - "expect": "visible", - "name": "Files...", - "role": "menu item", - "label": "Analyze menu shows entries" - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Close the Analyze menu" - }, - { - "do": "click", - "name": "Help", - "role": "menu item", - "label": "Open the Help menu" - }, - { - "expect": "visible", - "name": "Contents", - "role": "menu item", - "label": "Help menu shows entries" - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Close the Help menu" - } - ], - "script_for": "647c96907388" - }, - { - "id": "t1cc2be0f", - "name": "Show and hide toolbars", - "steps": [ - "Start the application with no project loaded.", - "Open View > Toolbar. All entries are checked.", - "Uncheck the entries one at a time." - ], - "expected": "A toolbar that is unchecked is no longer shown in the main window. The toolbars that are still checked are shown.", - "script": [ - { - "do": "click", - "name": "View", - "role": "menu item", - "label": "Open View menu" - }, - { - "do": "click", - "name": "Toolbars", - "role": "menu item", - "label": "Open Toolbars submenu" - }, - { - "expect": "state", - "name": "Standard", - "role": "menu item", - "state": "checked", - "label": "Standard toolbar entry is checked" - }, - { - "expect": "state", - "name": "Categories", - "role": "menu item", - "state": "checked", - "label": "Categories toolbar entry is checked" - }, - { - "expect": "state", - "name": "Filter", - "role": "menu item", - "state": "checked", - "label": "Filter toolbar entry is checked" - }, - { - "do": "click", - "name": "Standard", - "role": "menu item", - "label": "Uncheck Standard toolbar" - }, - { - "expect": "gone", - "name": "Standard", - "role": "tool bar", - "label": "Standard toolbar is no longer shown" - }, - { - "expect": "visible", - "name": "Categories", - "role": "tool bar", - "label": "Categories toolbar still shown" - }, - { - "expect": "visible", - "name": "Filter", - "role": "tool bar", - "label": "Filter toolbar still shown" - }, - { - "do": "click", - "name": "Categories", - "role": "menu item", - "label": "Uncheck Categories toolbar" - }, - { - "expect": "gone", - "name": "Categories", - "role": "tool bar", - "label": "Categories toolbar is no longer shown" - }, - { - "expect": "visible", - "name": "Filter", - "role": "tool bar", - "label": "Filter toolbar still shown" - }, - { - "do": "click", - "name": "Filter", - "role": "menu item", - "label": "Uncheck Filter toolbar" - }, - { - "expect": "gone", - "name": "Filter", - "role": "tool bar", - "label": "Filter toolbar is no longer shown" - } - ], - "script_for": "7672e4ede060" - }, - { - "id": "t5fdeee58", - "name": "Same version in the title and in About", - "steps": [ - "Start the application with no project loaded.", - "Note the version in the title of the main window (Cppcheck ).", - "Open Help > About." - ], - "expected": "The About dialog shows the same version as the title of the main window.", - "script": [ - { - "expect": "capture", - "name": "Cppcheck", - "role": "frame", - "regex": "'Cppcheck (?:Premium )?(\\d[^']*)'", - "as": "version", - "label": "Note the version in the title of the main window" - }, - { - "do": "click", - "name": "Help", - "role": "menu item", - "label": "Open the Help menu" - }, - { - "do": "click", - "name": "About...", - "role": "menu item", - "label": "Open Help > About" - }, - { - "expect": "visible", - "name": "About Cppcheck", - "role": "dialog", - "label": "About dialog is shown" - }, - { - "expect": "visible", - "name": "Version {version}", - "role": "label", - "label": "About dialog shows the same version as the main window title ({version})" - } - ], - "editions": [ - "opensource" - ], - "script_for": "1e35dcba1b6d" - }, - { - "id": "t90321d9c", - "name": "Help contents", - "steps": [ - "Start the application with no project loaded.", - "Open Help > Contents." - ], - "expected": "A help window opens and it has contents (it is not empty).", - "script": [ - { - "do": "click", - "name": "Help", - "role": "menu item", - "label": "Open the Help menu" - }, - { - "do": "click", - "name": "Contents", - "role": "menu item", - "label": "Choose Help > Contents" - }, - { - "expect": "visible", - "name": "Cppcheck GUI help", - "role": "dialog", - "label": "A help window opens", - "observed_fail": true - }, - { - "expect": "text", - "contains": "Contents", - "label": "The help window has contents (it is not empty)" - }, - { - "expect": "text", - "not_contains": "was not found", - "label": "No error that the help file was not found" - } - ], - "script_for": "eabc2cbcf729" - }, - { - "id": "tc059b730", - "name": "Other Help menu entries", - "steps": [ - "Start the application with no project loaded.", - "Open each of the other entries of the Help menu in turn (About and the rest). Close each window with Escape or its Close button." - ], - "expected": "Every entry opens a dialog or window with text in it. None is empty.", - "script": [ - { - "do": "click", - "name": "Help", - "role": "menu item", - "label": "Open the Help menu" - }, - { - "do": "click", - "name": "License...", - "role": "menu item", - "label": "Open License" - }, - { - "expect": "visible", - "name": "License", - "role": "dialog", - "label": "License dialog opens" - }, - { - "expect": "text", - "contains": "GNU GENERAL PUBLIC LICENSE", - "label": "License dialog has text" - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Close License with Escape" - }, - { - "expect": "gone", - "name": "License", - "label": "License dialog is closed" - }, - { - "do": "click", - "name": "Help", - "role": "menu item", - "label": "Open the Help menu" - }, - { - "do": "click", - "name": "Authors...", - "role": "menu item", - "label": "Open Authors" - }, - { - "expect": "visible", - "name": "Authors", - "role": "dialog", - "label": "Authors dialog opens" - }, - { - "expect": "text", - "contains": "The cppcheck team", - "label": "Authors dialog has text" - }, - { - "do": "click", - "name": "Close", - "role": "button", - "label": "Close Authors with its Close button" - }, - { - "expect": "gone", - "name": "Authors", - "role": "dialog", - "label": "Authors dialog is closed" - }, - { - "do": "click", - "name": "Help", - "role": "menu item", - "label": "Open the Help menu" - }, - { - "do": "click", - "name": "About...", - "role": "menu item", - "label": "Open About" - }, - { - "expect": "visible", - "name": "About Cppcheck", - "role": "dialog", - "label": "About dialog opens" - }, - { - "expect": "text", - "contains": "Cppcheck - A tool for static C/C++ code analysis.", - "label": "About dialog has text" - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Close About with Escape" - }, - { - "expect": "gone", - "name": "About Cppcheck", - "role": "dialog", - "label": "About dialog is closed" - }, - { - "do": "click", - "name": "Help", - "role": "menu item", - "label": "Open the Help menu" - }, - { - "do": "click", - "name": "Contents", - "role": "menu item", - "label": "Open Contents" - }, - { - "expect": "visible", - "name": "Cppcheck", - "role": "alert", - "label": "Contents shows a message" - }, - { - "expect": "text", - "contains": "Helpfile", - "label": "Message has text" - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Close the message with Escape" - }, - { - "expect": "visible", - "name": "Cppcheck GUI help", - "role": "dialog", - "label": "Help window opens" - }, - { - "expect": "text", - "contains": "Introduction", - "observed_fail": true, - "label": "Help window has text in it, not empty" - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Close the help window with Escape" - } - ], - "script_for": "63a2968914df" - }, - { - "id": "t5bf8f6ae", - "name": "Scratch pad", - "ticket": "#13982", - "steps": [ - "Start the application with no project loaded.", - "Open View > Scratch Pad.", - "Enter the code \"int main(void)\", \"{\", \" int *p = 0;\", \" return *p;\", \"}\" (one line each) and click Check." - ], - "expected": "The code is analysed: a nullPointer error is shown and the application does not crash.", - "script": [ - { - "do": "click", - "name": "View", - "role": "menu item", - "label": "Open the View menu" - }, - { - "do": "click", - "name": "Show Scratchpad", - "role": "menu item", - "label": "Open View > Scratch Pad" - }, - { - "expect": "visible", - "name": "Scratchpad", - "role": "dialog", - "label": "Scratchpad dialog is shown" - }, - { - "do": "focus", - "path": "1.1", - "label": "Focus the code editor of the scratchpad" - }, - { - "do": "type", - "value": "int main(void)", - "label": "Type line 1" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "New line" - }, - { - "do": "type", - "value": "{", - "label": "Type line 2" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "New line" - }, - { - "do": "type", - "value": " int *p = 0;", - "label": "Type line 3" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "New line" - }, - { - "do": "type", - "value": " return *p;", - "label": "Type line 4" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "New line" - }, - { - "do": "type", - "value": "}", - "label": "Type line 5" - }, - { - "do": "click", - "name": "Check", - "role": "button", - "label": "Click Check" - }, - { - "expect": "text", - "contains": "nullPointer", - "timeout": 15, - "label": "A nullPointer error is shown" - }, - { - "expect": "text", - "contains": "Null pointer dereference: p", - "label": "The null pointer message is shown" - }, - { - "expect": "alive", - "label": "The application did not crash" - } - ], - "script_for": "d09d0e350bbd" - }, - { - "id": "t1a2285d8", - "name": "Quit from the File menu", - "steps": [ - "Start the application with no project loaded.", - "Open File > Quit." - ], - "expected": "The application closes.", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Quit", - "role": "menu item", - "label": "Choose File > Quit" - }, - { - "expect": "exited", - "label": "The application closes" - } - ], - "script_for": "dfb4abb73512" - } - ] -} diff --git a/gui/manualtest/tests/application.scripts.json b/gui/manualtest/tests/application.scripts.json new file mode 100644 index 00000000000..98f5b20a42d --- /dev/null +++ b/gui/manualtest/tests/application.scripts.json @@ -0,0 +1,548 @@ +{ + "t41f9102b": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "expect": "visible", + "name": "Open XML...", + "role": "menu item", + "label": "File menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the File menu" + }, + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "expect": "visible", + "name": "Clear results", + "role": "menu item", + "label": "Edit menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the Edit menu" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "expect": "visible", + "name": "Toolbars", + "role": "menu item", + "label": "View menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the View menu" + }, + { + "do": "click", + "name": "Analyze", + "role": "menu item", + "label": "Open the Analyze menu" + }, + { + "expect": "visible", + "name": "Files...", + "role": "menu item", + "label": "Analyze menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the Analyze menu" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "expect": "visible", + "name": "Contents", + "role": "menu item", + "label": "Help menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the Help menu" + } + ], + "script_for": "06424398d415" + }, + "t1cc2be0f": { + "script": [ + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open View menu" + }, + { + "do": "click", + "name": "Toolbars", + "role": "menu item", + "label": "Open Toolbars submenu" + }, + { + "expect": "state", + "name": "Standard", + "role": "menu item", + "state": "checked", + "label": "Standard toolbar entry is checked" + }, + { + "expect": "state", + "name": "Categories", + "role": "menu item", + "state": "checked", + "label": "Categories toolbar entry is checked" + }, + { + "expect": "state", + "name": "Filter", + "role": "menu item", + "state": "checked", + "label": "Filter toolbar entry is checked" + }, + { + "do": "click", + "name": "Standard", + "role": "menu item", + "label": "Uncheck Standard toolbar" + }, + { + "expect": "gone", + "name": "Standard", + "role": "tool bar", + "label": "Standard toolbar is no longer shown" + }, + { + "expect": "visible", + "name": "Categories", + "role": "tool bar", + "label": "Categories toolbar still shown" + }, + { + "expect": "visible", + "name": "Filter", + "role": "tool bar", + "label": "Filter toolbar still shown" + }, + { + "do": "click", + "name": "Categories", + "role": "menu item", + "label": "Uncheck Categories toolbar" + }, + { + "expect": "gone", + "name": "Categories", + "role": "tool bar", + "label": "Categories toolbar is no longer shown" + }, + { + "expect": "visible", + "name": "Filter", + "role": "tool bar", + "label": "Filter toolbar still shown" + }, + { + "do": "click", + "name": "Filter", + "role": "menu item", + "label": "Uncheck Filter toolbar" + }, + { + "expect": "gone", + "name": "Filter", + "role": "tool bar", + "label": "Filter toolbar is no longer shown" + } + ], + "script_for": "6a99d7841493" + }, + "t5fdeee58": { + "script": [ + { + "expect": "capture", + "name": "Cppcheck", + "role": "frame", + "regex": "'Cppcheck (?:Premium )?(\\d[^']*)'", + "as": "version", + "label": "Note the version in the title of the main window" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "About...", + "role": "menu item", + "label": "Open Help > About" + }, + { + "expect": "visible", + "name": "About Cppcheck", + "role": "dialog", + "label": "About dialog is shown" + }, + { + "expect": "visible", + "name": "Version {version}", + "role": "label", + "label": "About dialog shows the same version as the main window title ({version})" + } + ], + "script_for": "5dadee22b5ec" + }, + "t90321d9c": { + "script": [ + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "Contents", + "role": "menu item", + "label": "Choose Help > Contents" + }, + { + "expect": "visible", + "name": "Cppcheck GUI help", + "role": "dialog", + "label": "A help window opens", + "observed_fail": true + }, + { + "expect": "text", + "contains": "Contents", + "label": "The help window has contents (it is not empty)" + }, + { + "expect": "text", + "not_contains": "was not found", + "label": "No error that the help file was not found" + } + ], + "script_for": "7c97b218aa0f" + }, + "tc059b730": { + "script": [ + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "License...", + "role": "menu item", + "label": "Open License" + }, + { + "expect": "visible", + "name": "License", + "role": "dialog", + "label": "License dialog opens" + }, + { + "expect": "text", + "contains": "GNU GENERAL PUBLIC LICENSE", + "label": "License dialog has text" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close License with Escape" + }, + { + "expect": "gone", + "name": "License", + "label": "License dialog is closed" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "Authors...", + "role": "menu item", + "label": "Open Authors" + }, + { + "expect": "visible", + "name": "Authors", + "role": "dialog", + "label": "Authors dialog opens" + }, + { + "expect": "text", + "contains": "The cppcheck team", + "label": "Authors dialog has text" + }, + { + "do": "click", + "name": "Close", + "role": "button", + "label": "Close Authors with its Close button" + }, + { + "expect": "gone", + "name": "Authors", + "role": "dialog", + "label": "Authors dialog is closed" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "About...", + "role": "menu item", + "label": "Open About" + }, + { + "expect": "visible", + "name": "About Cppcheck", + "role": "dialog", + "label": "About dialog opens" + }, + { + "expect": "text", + "contains": "Cppcheck - A tool for static C/C++ code analysis.", + "label": "About dialog has text" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close About with Escape" + }, + { + "expect": "gone", + "name": "About Cppcheck", + "role": "dialog", + "label": "About dialog is closed" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "Contents", + "role": "menu item", + "label": "Open Contents" + }, + { + "expect": "visible", + "name": "Cppcheck", + "role": "alert", + "label": "Contents shows a message" + }, + { + "expect": "text", + "contains": "Helpfile", + "label": "Message has text" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the message with Escape" + }, + { + "expect": "visible", + "name": "Cppcheck GUI help", + "role": "dialog", + "label": "Help window opens" + }, + { + "expect": "text", + "contains": "Introduction", + "observed_fail": true, + "label": "Help window has text in it, not empty" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the help window with Escape" + } + ], + "script_for": "97511d2313db" + }, + "t5bf8f6ae": { + "script": [ + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Show Scratchpad", + "role": "menu item", + "label": "Open View > Scratch Pad" + }, + { + "expect": "visible", + "name": "Scratchpad", + "role": "dialog", + "label": "Scratchpad dialog is shown" + }, + { + "do": "focus", + "path": "1.1", + "label": "Focus the code editor of the scratchpad" + }, + { + "do": "type", + "value": "int main(void)", + "label": "Type line 1" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": "{", + "label": "Type line 2" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": " int *p = 0;", + "label": "Type line 3" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": " return *p;", + "label": "Type line 4" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": "}", + "label": "Type line 5" + }, + { + "do": "click", + "name": "Check", + "role": "button", + "label": "Click Check" + }, + { + "expect": "text", + "contains": "nullPointer", + "timeout": 15, + "label": "A nullPointer error is shown" + }, + { + "expect": "text", + "contains": "Null pointer dereference: p", + "label": "The null pointer message is shown" + }, + { + "expect": "alive", + "label": "The application did not crash" + } + ], + "script_for": "30d9d26803fd" + }, + "t1a2285d8": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Quit", + "role": "menu item", + "label": "Choose File > Quit" + }, + { + "expect": "exited", + "label": "The application closes" + } + ], + "script_for": "bc31dd4069bf" + } +} diff --git a/gui/manualtest/tests/preferences.feature b/gui/manualtest/tests/preferences.feature new file mode 100644 index 00000000000..62e741f4dc7 --- /dev/null +++ b/gui/manualtest/tests/preferences.feature @@ -0,0 +1,42 @@ +Feature: Preferences + + Background: + Given the application is started with no project loaded + + @id:t232a9783 + Scenario: Default number of threads + When I open Edit > Preferences + And I look at the General tab + Then the number of threads is 1 + + @id:tf5b5a7e1 + Scenario: Number of threads cannot be 0 + Given Edit > Preferences is open on the General tab + When I type 0 in the number of threads field + And I close the dialog with OK + And I open Edit > Preferences again + Then the field has not accepted 0 + And the number of threads is at least 1, it is never 0 + + @id:t841cca6c @project:basic-project + Scenario: Analysis with 100 threads + Given the number of threads is set to 100 in Edit > Preferences and the dialog is closed with OK + When I open the project file {project:basic-project}/basic.cppcheck from the File menu + And I wait until the analysis is done + Then the application does not crash + And the analysis is done and the results are shown + + @id:t28ce8afe @project:embedded @edition:opensource + Scenario Outline: Misra rule text file + Given Edit > Preferences is open on the Addons tab + When I select the rule text file {project:embedded}/ + And I close the dialog with OK + And I open the project file {project:embedded}/embedded.cppcheck + And I wait until the analysis is done + Then the Misra error for the unused parameter has the rule text from the file (TEST RULE TEXT: unused parameters are not allowed) + And there are no internalError results + + Examples: + | rulefile | + | rules.txt | + | rules with spaces.txt | diff --git a/gui/manualtest/tests/preferences.json b/gui/manualtest/tests/preferences.json deleted file mode 100644 index 5eda00d6410..00000000000 --- a/gui/manualtest/tests/preferences.json +++ /dev/null @@ -1,357 +0,0 @@ -{ - "title": "Preferences", - "tests": [ - { - "id": "t232a9783", - "name": "Default number of threads", - "steps": [ - "Start the application with no project loaded.", - "Open Edit > Preferences and look at the General tab." - ], - "expected": "The number of threads is 1.", - "script": [ - { - "do": "click", - "name": "Edit", - "role": "menu item", - "label": "Open the Edit menu" - }, - { - "do": "click", - "name": "Preferences", - "role": "menu item", - "label": "Open Edit > Preferences" - }, - { - "expect": "visible", - "name": "Preferences", - "role": "dialog", - "label": "Preferences dialog is shown" - }, - { - "expect": "state", - "name": "General", - "role": "page tab", - "state": "selected", - "label": "General tab is shown" - }, - { - "expect": "value", - "name": "Number of threads", - "role": "text", - "equals": "1", - "label": "The number of threads is 1" - } - ], - "script_for": "dbe9c4176eff" - }, - { - "id": "tf5b5a7e1", - "name": "Number of threads cannot be 0", - "steps": [ - "Start the application with no project loaded.", - "Open Edit > Preferences and go to the General tab.", - "Type 0 in the number of threads field and close the dialog with OK.", - "Open Edit > Preferences again." - ], - "expected": "The field does not accept 0: the number of threads is at least 1, it is never 0.", - "script": [ - { - "do": "click", - "name": "Edit", - "role": "menu item", - "label": "Open the Edit menu" - }, - { - "do": "click", - "name": "Preferences", - "role": "menu item", - "label": "Open Edit > Preferences" - }, - { - "expect": "visible", - "name": "Preferences", - "role": "dialog", - "label": "Preferences dialog is shown" - }, - { - "do": "click", - "name": "General", - "role": "page tab", - "label": "Go to the General tab" - }, - { - "do": "settext", - "name": "Number of threads", - "role": "text", - "value": "0", - "label": "Type 0 in the number of threads field" - }, - { - "do": "click", - "name": "OK", - "role": "button", - "label": "Close the dialog with OK" - }, - { - "do": "click", - "name": "Edit", - "role": "menu item", - "label": "Open the Edit menu again" - }, - { - "do": "click", - "name": "Preferences", - "role": "menu item", - "label": "Open Edit > Preferences again" - }, - { - "expect": "visible", - "name": "Preferences", - "role": "dialog", - "label": "Preferences dialog is shown again" - }, - { - "do": "click", - "name": "General", - "role": "page tab", - "label": "Go to the General tab again" - }, - { - "expect": "value", - "name": "Number of threads", - "role": "text", - "equals": "1", - "label": "The number of threads is at least 1, never 0" - } - ], - "script_for": "385e7e11379e" - }, - { - "id": "t841cca6c", - "name": "Analysis with 100 threads", - "steps": [ - "Start the application with no project loaded.", - "Open Edit > Preferences, set the number of threads to 100 and close the dialog with OK.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done." - ], - "expected": "The application does not crash: the analysis is done and the results are shown.", - "script": [ - { - "do": "click", - "name": "Edit", - "role": "menu item", - "label": "Open the Edit menu" - }, - { - "do": "click", - "name": "Preferences", - "role": "menu item", - "label": "Open Preferences from the Edit menu" - }, - { - "expect": "visible", - "name": "Preferences", - "role": "dialog", - "label": "Preferences dialog is shown" - }, - { - "do": "settext", - "name": "Number of threads", - "role": "text", - "value": "100", - "label": "Set the number of threads to 100" - }, - { - "expect": "value", - "name": "Number of threads", - "role": "text", - "equals": "100", - "label": "Thread count field holds 100" - }, - { - "do": "click", - "name": "OK", - "role": "button", - "label": "Close the Preferences dialog with OK" - }, - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "expect": "visible", - "name": "Select Project File", - "role": "dialog", - "label": "File dialog is shown" - }, - { - "do": "settext", - "name": "File name", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the path of basic.cppcheck" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Open the project file" - }, - { - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "timeout": 60, - "label": "Analysis is done and results for src/b.c are shown" - }, - { - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "label": "Results for src/a.c are shown" - }, - { - "expect": "text", - "contains": "Project: basic.cppcheck", - "label": "The project is loaded" - }, - { - "expect": "alive", - "label": "The application did not crash" - } - ], - "projects": [ - "basic-project" - ], - "script_for": "8888c70e7498" - }, - { - "id": "t28ce8afe", - "name": "Misra rule text file", - "steps": [ - "Start the application with no project loaded.", - "Open Edit > Preferences and go to the Addons tab.", - "Select the rule text file {project:embedded}/{rulefile} and close the dialog with OK.", - "Open the project file {project:embedded}/embedded.cppcheck and wait until the analysis is done." - ], - "expected": "The Misra error for the unused parameter has the rule text from the file (TEST RULE TEXT: unused parameters are not allowed). There are no internalError results.", - "script": [ - { - "do": "click", - "name": "Preferences", - "role": "button", - "label": "Open Preferences" - }, - { - "do": "click", - "name": "Addons", - "role": "page tab", - "label": "Go to the Addons tab" - }, - { - "do": "settext", - "name": "MISRA addon", - "role": "text", - "value": "{project:embedded}/{rulefile}", - "label": "Select the rule text file" - }, - { - "do": "click", - "name": "OK", - "role": "button", - "label": "Close the dialog with OK" - }, - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name", - "role": "text", - "value": "{project:embedded}/embedded.cppcheck", - "label": "Enter the project file name" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Open the project file" - }, - { - "do": "sleep", - "seconds": 8, - "label": "Wait until the analysis is done" - }, - { - "expect": "visible", - "name": "gpio.c", - "role": "table cell", - "timeout": 20, - "label": "Results for gpio.c are shown" - }, - { - "do": "doubleclick", - "name": "gpio.c", - "role": "table cell", - "label": "Select the gpio.c result row" - }, - { - "do": "key", - "keys": [ - "Right" - ], - "label": "Expand the result row" - }, - { - "expect": "text", - "contains": "TEST RULE TEXT: unused parameters are not allowed", - "timeout": 10, - "label": "Misra error has the rule text from the file" - }, - { - "expect": "text", - "contains": "misra-c2012-2.7", - "label": "The unused parameter Misra rule id is shown" - }, - { - "expect": "text", - "not_contains": "internalError", - "label": "There are no internalError results" - } - ], - "projects": [ - "embedded" - ], - "editions": [ - "opensource" - ], - "matrix": { - "rulefile": [ - "rules.txt", - "rules with spaces.txt" - ] - }, - "script_for": "d3383bf3bd02" - } - ] -} diff --git a/gui/manualtest/tests/preferences.scripts.json b/gui/manualtest/tests/preferences.scripts.json new file mode 100644 index 00000000000..2da801e7e71 --- /dev/null +++ b/gui/manualtest/tests/preferences.scripts.json @@ -0,0 +1,306 @@ +{ + "t232a9783": { + "script": [ + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Edit > Preferences" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown" + }, + { + "expect": "state", + "name": "General", + "role": "page tab", + "state": "selected", + "label": "General tab is shown" + }, + { + "expect": "value", + "name": "Number of threads", + "role": "text", + "equals": "1", + "label": "The number of threads is 1" + } + ], + "script_for": "3c6d225d76b6" + }, + "tf5b5a7e1": { + "script": [ + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Edit > Preferences" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown" + }, + { + "do": "click", + "name": "General", + "role": "page tab", + "label": "Go to the General tab" + }, + { + "do": "settext", + "name": "Number of threads", + "role": "text", + "value": "0", + "label": "Type 0 in the number of threads field" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Close the dialog with OK" + }, + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu again" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Edit > Preferences again" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown again" + }, + { + "do": "click", + "name": "General", + "role": "page tab", + "label": "Go to the General tab again" + }, + { + "expect": "value", + "name": "Number of threads", + "role": "text", + "equals": "1", + "label": "The number of threads is at least 1, never 0" + } + ], + "script_for": "6a7660844e11" + }, + "t841cca6c": { + "script": [ + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Preferences from the Edit menu" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown" + }, + { + "do": "settext", + "name": "Number of threads", + "role": "text", + "value": "100", + "label": "Set the number of threads to 100" + }, + { + "expect": "value", + "name": "Number of threads", + "role": "text", + "equals": "100", + "label": "Thread count field holds 100" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Close the Preferences dialog with OK" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "expect": "visible", + "name": "Select Project File", + "role": "dialog", + "label": "File dialog is shown" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 60, + "label": "Analysis is done and results for src/b.c are shown" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "Results for src/a.c are shown" + }, + { + "expect": "text", + "contains": "Project: basic.cppcheck", + "label": "The project is loaded" + }, + { + "expect": "alive", + "label": "The application did not crash" + } + ], + "script_for": "3f99ba3a159c" + }, + "t28ce8afe": { + "script": [ + { + "do": "click", + "name": "Preferences", + "role": "button", + "label": "Open Preferences" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "do": "settext", + "name": "MISRA addon", + "role": "text", + "value": "{project:embedded}/{rulefile}", + "label": "Select the rule text file" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Close the dialog with OK" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:embedded}/embedded.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "do": "sleep", + "seconds": 8, + "label": "Wait until the analysis is done" + }, + { + "expect": "visible", + "name": "gpio.c", + "role": "table cell", + "timeout": 20, + "label": "Results for gpio.c are shown" + }, + { + "do": "doubleclick", + "name": "gpio.c", + "role": "table cell", + "label": "Select the gpio.c result row" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand the result row" + }, + { + "expect": "text", + "contains": "TEST RULE TEXT: unused parameters are not allowed", + "timeout": 10, + "label": "Misra error has the rule text from the file" + }, + { + "expect": "text", + "contains": "misra-c2012-2.7", + "label": "The unused parameter Misra rule id is shown" + }, + { + "expect": "text", + "not_contains": "internalError", + "label": "There are no internalError results" + } + ], + "script_for": "755194f793e0" + } +} diff --git a/gui/manualtest/tests/project_dialog.feature b/gui/manualtest/tests/project_dialog.feature new file mode 100644 index 00000000000..2d28b7dbc0e --- /dev/null +++ b/gui/manualtest/tests/project_dialog.feature @@ -0,0 +1,40 @@ +Feature: Project file dialog + + Background: + Given the application is started with no project loaded + + @id:tcf7ddb76 @ticket:14983 @project:relative-paths + Scenario: Relative paths in the saved project + Given a new project file is created and the project file dialog is open + When I import the project {project:relative-paths}/compile_commands.json (Paths and defines, Import project) + And I add the include directory {project:relative-paths}/inc + And I exclude the file {project:relative-paths}/skip/b.c + And I save the project file as {project:relative-paths}/rel.cppcheck + Then in the saved rel.cppcheck the import project is written relative to the project file: compile_commands.json + And the include directory is written relative to the project file: inc + And the excluded file is written relative to the project file: skip/b.c + And the XML contains no absolute path to the project folder + + @id:t5afff002 @ticket:14489 + Scenario: Select the platform pic8.xml + Given a new project file is created and the project file dialog is open + When I open the Platform drop-down list + And I select pic8.xml in the list + Then pic8.xml is in the Platform list + And pic8.xml is the platform that the dialog shows + + @id:t91490557 @ticket:14672 @project:custom-cfg + Scenario: Custom cfg file in the library list + Given the folder of {project:custom-cfg}/p.cppcheck also holds aa.cfg, a copy of cfg/avr.cfg + When I open the project file {project:custom-cfg}/p.cppcheck + And I open the project file dialog (edit the project file) + And I look at the library list (Libraries) + Then aa is in the library list + And aa comes before avr, the list is in alphabetical order + + @id:t0ec0680b @ticket:14488 @project:embedded @edition:opensource + Scenario: Misra checkbox of a loaded project + When I open the project file {project:embedded}/embedded.cppcheck + And I open the project file dialog (edit the project file) + And I go to the Addons tab + Then the Misra C 2012 checkbox is checked diff --git a/gui/manualtest/tests/project_dialog.json b/gui/manualtest/tests/project_dialog.json deleted file mode 100644 index e1779d48f9d..00000000000 --- a/gui/manualtest/tests/project_dialog.json +++ /dev/null @@ -1,428 +0,0 @@ -{ - "title": "Project file dialog", - "tests": [ - { - "id": "tcf7ddb76", - "name": "Relative paths in the saved project", - "ticket": "#14983", - "steps": [ - "Start the application with no project loaded.", - "Create a new project file and open the project file dialog.", - "Import the project {project:relative-paths}/compile_commands.json (Paths and defines, Import project).", - "Add the include directory {project:relative-paths}/inc.", - "Exclude the file {project:relative-paths}/skip/b.c.", - "Save the project file as {project:relative-paths}/rel.cppcheck." - ], - "expected": "In the saved rel.cppcheck the import project, the include directory and the excluded file are written relative to the project file (compile_commands.json, inc, skip/b.c). The XML contains no absolute path to the project folder.", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "New Project File", - "role": "menu item", - "label": "Create a new project file" - }, - { - "do": "settext", - "name": "File name", - "role": "text", - "value": "{project:relative-paths}/rel.cppcheck", - "label": "Enter the project file name" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the project file name" - }, - { - "expect": "visible", - "name": "Project file: rel.cppcheck", - "role": "dialog", - "label": "Project file dialog is open" - }, - { - "do": "click", - "name": "Add...", - "role": "button", - "nth": 1, - "label": "Add an include directory" - }, - { - "do": "settext", - "name": "Directory:", - "role": "text", - "value": "{project:relative-paths}/inc", - "label": "Enter the include directory {project:relative-paths}/inc" - }, - { - "do": "click", - "name": "Choose", - "role": "button", - "label": "Choose the include directory" - }, - { - "do": "click", - "name": "Warning options", - "role": "page tab", - "label": "Open the tab with the excluded files" - }, - { - "do": "click", - "name": "Exclude file", - "role": "button", - "label": "Exclude a file" - }, - { - "do": "settext", - "name": "File name", - "role": "text", - "value": "{project:relative-paths}/skip/b.c", - "label": "Enter the excluded file {project:relative-paths}/skip/b.c" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the excluded file" - }, - { - "expect": "visible", - "name": "skip/b.c", - "role": "list item", - "label": "skip/b.c is in the exclude list" - }, - { - "do": "click", - "name": "Paths and Defines", - "role": "page tab", - "label": "Back to Paths and Defines" - }, - { - "do": "click", - "name": "Browse...", - "role": "button", - "nth": 0, - "label": "Browse for the import project" - }, - { - "do": "settext", - "name": "File name", - "role": "text", - "value": "{project:relative-paths}/compile_commands.json", - "label": "Enter the import project {project:relative-paths}/compile_commands.json" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the import project" - }, - { - "do": "click", - "name": "OK", - "role": "button", - "label": "Save the project file as rel.cppcheck" - }, - { - "do": "sleep", - "seconds": 1 - }, - { - "do": "key", - "keys": [ - "Escape" - ], - "label": "Dismiss the build dir question" - }, - { - "expect": "file", - "path": "{project:relative-paths}/rel.cppcheck", - "contains": "compile_commands.json", - "label": "Import project is relative" - }, - { - "expect": "file", - "path": "{project:relative-paths}/rel.cppcheck", - "contains": "", - "label": "Excluded file is relative (skip/b.c)" - }, - { - "expect": "file", - "path": "{project:relative-paths}/rel.cppcheck", - "not_contains": "{project:relative-paths}", - "label": "No absolute path to the project folder in the XML" - } - ], - "projects": [ - "relative-paths" - ], - "script_for": "23c19e2db208" - }, - { - "id": "t5afff002", - "name": "Select the platform pic8.xml", - "ticket": "#14489", - "steps": [ - "Start the application with no project loaded.", - "Create a new project file and open the project file dialog.", - "Open the Platform drop-down list.", - "Select pic8.xml in the list." - ], - "expected": "pic8.xml is in the Platform list, it can be selected and it is then the platform that the dialog shows.", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "New Project File", - "role": "menu item", - "label": "Create a new project file" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{work}/t.cppcheck", - "label": "Enter the project file name" - }, - { - "do": "click", - "name": "Save", - "role": "button", - "exact": true, - "label": "Save the new project file; the project file dialog opens" - }, - { - "expect": "visible", - "name": "Project file", - "role": "dialog", - "label": "Project file dialog is open" - }, - { - "do": "click", - "name": "Types and Functions", - "role": "page tab", - "label": "Go to the tab with the Platform setting" - }, - { - "do": "click", - "name": "Platform", - "role": "combo box", - "label": "Open the Platform drop-down list" - }, - { - "expect": "visible", - "name": "pic8.xml", - "role": "list item", - "label": "pic8.xml is in the Platform list" - }, - { - "do": "click", - "name": "pic8.xml", - "role": "list item", - "exact": true, - "mouse": true, - "label": "Select pic8.xml in the list" - }, - { - "expect": "visible", - "name": "pic8.xml", - "role": "combo box", - "label": "The dialog shows pic8.xml as the platform" - } - ], - "script_for": "83263650eda1" - }, - { - "id": "t91490557", - "name": "Custom cfg file in the library list", - "ticket": "#14672", - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:custom-cfg}/p.cppcheck. Its folder also holds aa.cfg, a copy of cfg/avr.cfg.", - "Open the project file dialog (edit the project file).", - "Look at the library list (Libraries)." - ], - "expected": "aa is in the library list in alphabetical order (before avr).", - "script": [ - { - "do": "click", - "name": "Open Project File", - "role": "button", - "label": "Open the project file dialog from the toolbar" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:custom-cfg}/p.cppcheck", - "label": "Enter the path of p.cppcheck" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Open the project file" - }, - { - "expect": "visible", - "name": "Cppcheck", - "role": "alert", - "label": "Analysis finishes and an alert appears" - }, - { - "do": "click", - "name": "OK", - "role": "button", - "label": "Dismiss the alert" - }, - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Edit Project File", - "role": "menu item", - "label": "Open the project file dialog (edit the project file)" - }, - { - "expect": "visible", - "name": "Project file: p.cppcheck", - "role": "dialog", - "label": "The project file dialog is open" - }, - { - "expect": "visible", - "name": "aa", - "role": "list item", - "exact": true, - "label": "aa is in the Libraries list" - }, - { - "expect": "visible", - "name": "avr", - "role": "list item", - "exact": true, - "label": "avr is in the Libraries list" - } - ], - "projects": [ - "custom-cfg" - ], - "script_for": "db079765d5b6" - }, - { - "id": "t0ec0680b", - "name": "Misra checkbox of a loaded project", - "ticket": "#14488", - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:embedded}/embedded.cppcheck.", - "Open the project file dialog (edit the project file).", - "Go to the Addons tab." - ], - "expected": "The Misra C 2012 checkbox is checked.", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "wait", - "name": "Select Project File", - "role": "dialog", - "label": "File dialog is shown" - }, - { - "do": "settext", - "name": "File name", - "role": "text", - "value": "{project:embedded}/embedded.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Open the project file" - }, - { - "do": "wait", - "name": "Project: embedded.cppcheck", - "role": "frame", - "label": "Project is loaded" - }, - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu again" - }, - { - "do": "click", - "name": "Edit Project File...", - "role": "menu item", - "label": "Open the project file dialog" - }, - { - "do": "wait", - "name": "Project file: embedded.cppcheck", - "role": "dialog", - "label": "Project file dialog is shown" - }, - { - "do": "click", - "name": "Addons", - "role": "page tab", - "label": "Go to the Addons tab" - }, - { - "expect": "state", - "name": "Misra C 2012", - "role": "check box", - "state": "checked", - "label": "The Misra C 2012 checkbox is checked" - } - ], - "projects": [ - "embedded" - ], - "editions": [ - "opensource" - ], - "script_for": "13561e8de354" - } - ] -} diff --git a/gui/manualtest/tests/project_dialog.scripts.json b/gui/manualtest/tests/project_dialog.scripts.json new file mode 100644 index 00000000000..08dfc5965ce --- /dev/null +++ b/gui/manualtest/tests/project_dialog.scripts.json @@ -0,0 +1,371 @@ +{ + "tcf7ddb76": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "New Project File", + "role": "menu item", + "label": "Create a new project file" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:relative-paths}/rel.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the project file name" + }, + { + "expect": "visible", + "name": "Project file: rel.cppcheck", + "role": "dialog", + "label": "Project file dialog is open" + }, + { + "do": "click", + "name": "Add...", + "role": "button", + "nth": 1, + "label": "Add an include directory" + }, + { + "do": "settext", + "name": "Directory:", + "role": "text", + "value": "{project:relative-paths}/inc", + "label": "Enter the include directory {project:relative-paths}/inc" + }, + { + "do": "click", + "name": "Choose", + "role": "button", + "label": "Choose the include directory" + }, + { + "do": "click", + "name": "Warning options", + "role": "page tab", + "label": "Open the tab with the excluded files" + }, + { + "do": "click", + "name": "Exclude file", + "role": "button", + "label": "Exclude a file" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:relative-paths}/skip/b.c", + "label": "Enter the excluded file {project:relative-paths}/skip/b.c" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the excluded file" + }, + { + "expect": "visible", + "name": "skip/b.c", + "role": "list item", + "label": "skip/b.c is in the exclude list" + }, + { + "do": "click", + "name": "Paths and Defines", + "role": "page tab", + "label": "Back to Paths and Defines" + }, + { + "do": "click", + "name": "Browse...", + "role": "button", + "nth": 0, + "label": "Browse for the import project" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:relative-paths}/compile_commands.json", + "label": "Enter the import project {project:relative-paths}/compile_commands.json" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the import project" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Save the project file as rel.cppcheck" + }, + { + "do": "sleep", + "seconds": 1 + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Dismiss the build dir question" + }, + { + "expect": "file", + "path": "{project:relative-paths}/rel.cppcheck", + "contains": "compile_commands.json", + "label": "Import project is relative" + }, + { + "expect": "file", + "path": "{project:relative-paths}/rel.cppcheck", + "contains": "", + "label": "Excluded file is relative (skip/b.c)" + }, + { + "expect": "file", + "path": "{project:relative-paths}/rel.cppcheck", + "not_contains": "{project:relative-paths}", + "label": "No absolute path to the project folder in the XML" + } + ], + "script_for": "edfc96b05beb" + }, + "t5afff002": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "New Project File", + "role": "menu item", + "label": "Create a new project file" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{work}/t.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "click", + "name": "Save", + "role": "button", + "exact": true, + "label": "Save the new project file; the project file dialog opens" + }, + { + "expect": "visible", + "name": "Project file", + "role": "dialog", + "label": "Project file dialog is open" + }, + { + "do": "click", + "name": "Types and Functions", + "role": "page tab", + "label": "Go to the tab with the Platform setting" + }, + { + "do": "click", + "name": "Platform", + "role": "combo box", + "label": "Open the Platform drop-down list" + }, + { + "expect": "visible", + "name": "pic8.xml", + "role": "list item", + "label": "pic8.xml is in the Platform list" + }, + { + "do": "click", + "name": "pic8.xml", + "role": "list item", + "exact": true, + "mouse": true, + "label": "Select pic8.xml in the list" + }, + { + "expect": "visible", + "name": "pic8.xml", + "role": "combo box", + "label": "The dialog shows pic8.xml as the platform" + } + ], + "script_for": "619f376b22e1" + }, + "t91490557": { + "script": [ + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the project file dialog from the toolbar" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:custom-cfg}/p.cppcheck", + "label": "Enter the path of p.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "Cppcheck", + "role": "alert", + "label": "Analysis finishes and an alert appears" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Dismiss the alert" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Edit Project File", + "role": "menu item", + "label": "Open the project file dialog (edit the project file)" + }, + { + "expect": "visible", + "name": "Project file: p.cppcheck", + "role": "dialog", + "label": "The project file dialog is open" + }, + { + "expect": "visible", + "name": "aa", + "role": "list item", + "exact": true, + "label": "aa is in the Libraries list" + }, + { + "expect": "visible", + "name": "avr", + "role": "list item", + "exact": true, + "label": "avr is in the Libraries list" + } + ], + "script_for": "b0bf245f7042" + }, + "t0ec0680b": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "File dialog is shown" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:embedded}/embedded.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "do": "wait", + "name": "Project: embedded.cppcheck", + "role": "frame", + "label": "Project is loaded" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu again" + }, + { + "do": "click", + "name": "Edit Project File...", + "role": "menu item", + "label": "Open the project file dialog" + }, + { + "do": "wait", + "name": "Project file: embedded.cppcheck", + "role": "dialog", + "label": "Project file dialog is shown" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "expect": "state", + "name": "Misra C 2012", + "role": "check box", + "state": "checked", + "label": "The Misra C 2012 checkbox is checked" + } + ], + "script_for": "187bd0d82a72" + } +} diff --git a/gui/manualtest/tests/projects.feature b/gui/manualtest/tests/projects.feature new file mode 100644 index 00000000000..33312524239 --- /dev/null +++ b/gui/manualtest/tests/projects.feature @@ -0,0 +1,72 @@ +Feature: Open and create projects + + Background: + Given the application is started with no project loaded + + @id:tf9fc8bf6 @project:basic-project + Scenario: Open a project file + When I open the project file {project:basic-project}/basic.cppcheck with the open project file entry in the File menu + Then the project opens + And the title of the main window names basic.cppcheck + And the analysis results are shown: nullPointer errors in a.c and util.h, arrayIndexOutOfBounds in b.c + + @id:t6bb11424 @project:switch-project + Scenario: Open a second project + TODO: with and without build dir + Given the project file {project:switch-project}/one/p1.cppcheck is opened + And the analysis is done + And the results show a nullPointer error in a.c + When I open the project file {project:switch-project}/two/p2.cppcheck + And I wait until the analysis is done + Then the results of the first project (nullPointer in a.c) are gone + And the results of the second project (arrayIndexOutOfBounds in b.c) are shown + + @id:t176fd17f @project:switch-project + Scenario: Open a recent project + Given the project file {project:switch-project}/one/p1.cppcheck is opened and analysed + And the project file {project:switch-project}/two/p2.cppcheck is opened and analysed + When I open the File menu + And I click p1.cppcheck in the list of recent projects + Then the project p1.cppcheck is opened + And the title of the main window names p1.cppcheck, not p2.cppcheck or another file + + @id:t1e899c1c @project:basic-project + Scenario: New project file + When I open File > New Project File + And I enter the file name {project:basic-project}/OSgui.cppcheck + And I add the folder {project:basic-project}/src as a path to check in the project file dialog + And I save the project file without selecting an addon + And I wait until the analysis is done + Then the project file OSgui.cppcheck is saved without any addon in it + And the analysis is run + And the results are shown: nullPointer errors in a.c, arrayIndexOutOfBounds in b.c + And util.h is not checked, the new project has no include path to it + + @id:t202b9515 @project:basic-project + Scenario Outline: New project file with an addon + When I open File > New Project File + And I enter the file name {project:basic-project}/OSgui.cppcheck + And I add the folder {project:basic-project}/src as a path to check in the project file dialog + And I tick the checkbox of the addon whose name contains "" on the Addons tab (Misra C, Y2038 or Thread safety) + And I save the project file + And I wait until the analysis is done + Then the project file OSgui.cppcheck is saved with an addon entry that contains "" (misra, y2038 or threadsafety) + And the analysis is run and results are shown + And none of the results is an internalError about the addon + + Examples: + | addon | + | misra | + | y2038 | + | thread | + + @id:tf6da2cfd @project:basic-project + Scenario: Add the Misra addon to a project + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I open File > Edit Project File + And I select MISRA C 2012 on the Addons tab + And I save the project file + And I wait until the analysis is done + Then basic.cppcheck has the addon misra in it + And the analysis is run again diff --git a/gui/manualtest/tests/projects.json b/gui/manualtest/tests/projects.json deleted file mode 100644 index 3fab3af0778..00000000000 --- a/gui/manualtest/tests/projects.json +++ /dev/null @@ -1,855 +0,0 @@ -{ - "title": "Open and create projects", - "tests": [ - { - "name": "Open a project file", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck with the open project file entry in the File menu." - ], - "expected": "The project opens: the title of the main window names basic.cppcheck and the analysis results are shown (nullPointer errors in a.c and util.h, arrayIndexOutOfBounds in b.c).", - "id": "tf9fc8bf6", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File in the File menu" - }, - { - "expect": "visible", - "name": "Select Project File", - "role": "dialog", - "label": "The project file dialog is shown" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the path of basic.cppcheck" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the dialog to open the project" - }, - { - "expect": "visible", - "name": "Project: basic.cppcheck", - "role": "frame", - "timeout": 15, - "label": "The title of the main window names basic.cppcheck" - }, - { - "do": "wait", - "name": "Select Project File", - "gone": true, - "label": "The dialog is closed" - }, - { - "do": "sleep", - "seconds": 4, - "label": "Wait for the analysis to finish" - }, - { - "do": "focus", - "role": "tree", - "label": "Focus the results tree" - }, - { - "do": "key", - "keys": [ - "Home", - "Down", - "Down", - "Right", - "Up", - "Right", - "Up", - "Right", - "Home" - ], - "label": "Expand the results of all three files and go to the top" - }, - { - "expect": "visible", - "name": "nullPointer", - "role": "table cell", - "timeout": 10, - "label": "nullPointer errors are shown for util.h and a.c" - }, - { - "expect": "text", - "contains": "src/a.c", - "label": "Results for a.c are shown" - }, - { - "expect": "text", - "contains": "include/util.h", - "label": "Results for util.h are shown" - }, - { - "do": "key", - "keys": [ - "End" - ], - "label": "Scroll to the results of b.c" - }, - { - "expect": "visible", - "name": "arrayIndexOutOfBounds", - "role": "table cell", - "timeout": 10, - "label": "arrayIndexOutOfBounds error is shown for b.c" - } - ], - "script_for": "be86887ae48f" - }, - { - "name": "Open a second project", - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:switch-project}/one/p1.cppcheck and wait until the analysis is done. The results show a nullPointer error in a.c.", - "Open the project file {project:switch-project}/two/p2.cppcheck and wait until the analysis is done." - ], - "expected": "The results of the first project (nullPointer in a.c) are gone and the results of the second project (arrayIndexOutOfBounds in b.c) are shown.", - "todo": "with and without build dir", - "projects": [ - "switch-project" - ], - "id": "t6bb11424", - "script": [ - { - "do": "click", - "name": "Open Project File", - "role": "button", - "label": "Open the first project" - }, - { - "do": "type", - "value": "{project:switch-project}/one/p1.cppcheck", - "label": "Enter path of p1.cppcheck" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the file dialog" - }, - { - "expect": "visible", - "name": "Project: p1.cppcheck", - "role": "frame", - "timeout": 10, - "label": "First project is loaded" - }, - { - "expect": "visible", - "name": "a.c", - "role": "table cell", - "timeout": 20, - "label": "Results of the first project show a.c" - }, - { - "do": "click", - "name": "Open Project File", - "role": "button", - "label": "Open the second project" - }, - { - "do": "type", - "value": "{project:switch-project}/two/p2.cppcheck", - "label": "Enter path of p2.cppcheck" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the file dialog" - }, - { - "expect": "visible", - "name": "Project: p2.cppcheck", - "role": "frame", - "timeout": 10, - "label": "Second project is loaded" - }, - { - "expect": "visible", - "name": "b.c", - "role": "table cell", - "timeout": 20, - "label": "Results of the second project show b.c" - }, - { - "expect": "gone", - "name": "a.c", - "role": "table cell", - "label": "Results of the first project are gone" - } - ], - "script_for": "2fe0df39eb8b" - }, - { - "name": "Open a recent project", - "projects": [ - "switch-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:switch-project}/one/p1.cppcheck and wait until the analysis is done.", - "Open the project file {project:switch-project}/two/p2.cppcheck and wait until the analysis is done.", - "Open the File menu and click p1.cppcheck in the list of recent projects." - ], - "expected": "The project p1.cppcheck is opened: the title of the main window names p1.cppcheck, not p2.cppcheck or another file.", - "id": "t176fd17f", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:switch-project}/one/p1.cppcheck", - "label": "Enter the path of p1.cppcheck" - }, - { - "do": "click", - "name": "Open", - "role": "button", - "exact": true, - "label": "Open p1.cppcheck" - }, - { - "expect": "text", - "contains": "Project: p1.cppcheck", - "timeout": 10, - "label": "Project p1 is open" - }, - { - "expect": "visible", - "name": "Checking a.c", - "timeout": 15, - "label": "Analysis of p1 is done" - }, - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:switch-project}/two/p2.cppcheck", - "label": "Enter the path of p2.cppcheck" - }, - { - "do": "click", - "name": "Open", - "role": "button", - "exact": true, - "label": "Open p2.cppcheck" - }, - { - "expect": "text", - "contains": "Project: p2.cppcheck", - "timeout": 10, - "label": "Project p2 is open" - }, - { - "do": "sleep", - "seconds": 3, - "label": "Wait until the analysis of p2 is done" - }, - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "p1.cppcheck", - "role": "menu item", - "label": "Click p1.cppcheck in the recent projects list" - }, - { - "expect": "text", - "contains": "Project: p1.cppcheck", - "timeout": 10, - "label": "Main window title names p1.cppcheck" - }, - { - "expect": "text", - "not_contains": "Project: p2.cppcheck", - "label": "Title does not name p2.cppcheck" - } - ], - "script_for": "1e971e851ea7" - }, - { - "name": "New project file", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open File > New Project File.", - "Enter the file name {project:basic-project}/OSgui.cppcheck.", - "In the project file dialog add the folder {project:basic-project}/src as a path to check.", - "Save the project file without selecting an addon and wait until the analysis is done." - ], - "expected": "The project file OSgui.cppcheck is saved without any addon in it and the analysis is run: the results (nullPointer errors in a.c, arrayIndexOutOfBounds in b.c) are shown. util.h is not checked, the new project has no include path to it.", - "id": "t1e899c1c", - "script": [ - { - "label": "Open File menu", - "do": "click", - "name": "File", - "role": "menu item" - }, - { - "label": "Open File > New Project File", - "do": "click", - "name": "New Project File", - "role": "menu item" - }, - { - "label": "Wait for the file name dialog", - "do": "wait", - "name": "Select Project Filename", - "role": "dialog" - }, - { - "label": "Enter the file name OSgui.cppcheck", - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/OSgui.cppcheck" - }, - { - "label": "Confirm the file name", - "do": "key", - "keys": [ - "Return" - ] - }, - { - "label": "Project file dialog is shown", - "expect": "visible", - "name": "Project file: OSgui.cppcheck", - "role": "dialog" - }, - { - "label": "Add a path to check", - "do": "click", - "name": "Add...", - "role": "button", - "nth": 0 - }, - { - "label": "Wait for the directory dialog", - "do": "wait", - "name": "Select a directory to check", - "role": "dialog" - }, - { - "label": "Enter the src folder", - "do": "settext", - "name": "Directory:", - "role": "text", - "value": "{project:basic-project}/src" - }, - { - "label": "Choose the src folder", - "do": "click", - "name": "Choose", - "role": "button" - }, - { - "label": "src is in the paths list", - "expect": "visible", - "name": "src", - "role": "list item" - }, - { - "label": "Save the project file without selecting an addon", - "do": "click", - "name": "OK", - "role": "button" - }, - { - "label": "Create the build dir when asked", - "do": "click", - "name": "Yes", - "role": "button" - }, - { - "label": "Wait until the analysis has checked b.c", - "expect": "text", - "contains": "Checking src/b.c", - "timeout": 30 - }, - { - "label": "Wait for the results of a.c", - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "timeout": 30 - }, - { - "label": "Select a.c results", - "do": "click", - "name": "src/a.c", - "role": "table cell", - "nth": 0, - "mouse": true - }, - { - "label": "Collapse a.c results to get a known state", - "do": "key", - "keys": [ - "Left" - ] - }, - { - "label": "Expand a.c results", - "do": "key", - "keys": [ - "Right" - ] - }, - { - "label": "nullPointer is reported in a.c", - "expect": "visible", - "name": "nullPointer", - "role": "table cell", - "timeout": 10 - }, - { - "label": "Select b.c results", - "do": "click", - "name": "src/b.c", - "role": "table cell", - "nth": 0, - "mouse": true - }, - { - "label": "Collapse b.c results to get a known state", - "do": "key", - "keys": [ - "Left" - ] - }, - { - "label": "Expand b.c results", - "do": "key", - "keys": [ - "Right" - ] - }, - { - "label": "arrayIndexOutOfBounds is reported in b.c", - "expect": "visible", - "name": "arrayIndexOutOfBounds", - "role": "table cell", - "timeout": 10 - }, - { - "label": "util.h is not checked", - "expect": "text", - "not_contains": "Checking include/util.h" - }, - { - "label": "Project file contains the src path", - "expect": "file", - "path": "{project:basic-project}/OSgui.cppcheck", - "contains": "" - }, - { - "label": "Project file has no addon", - "expect": "file", - "path": "{project:basic-project}/OSgui.cppcheck", - "not_contains": "addon" - }, - { - "label": "Project file has no include path", - "expect": "file", - "path": "{project:basic-project}/OSgui.cppcheck", - "not_contains": "include" - } - ], - "script_for": "1faee2696e8e" - }, - { - "name": "New project file with an addon", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open File > New Project File.", - "Enter the file name {project:basic-project}/OSgui.cppcheck.", - "In the project file dialog add the folder {project:basic-project}/src as a path to check.", - "On the Addons tab tick the checkbox of the addon whose name contains \"{addon}\" (Misra C, Y2038 or Thread safety).", - "Save the project file and wait until the analysis is done." - ], - "expected": "The project file OSgui.cppcheck is saved with an addon entry that contains \"{addon}\" (misra, y2038 or threadsafety) and the analysis is run: results are shown and none of them is an internalError about the addon.", - "id": "t202b9515", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "New Project File...", - "role": "menu item", - "label": "Open File > New Project File" - }, - { - "do": "wait", - "name": "Select Project Filename", - "role": "dialog", - "label": "Wait for the file name dialog" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/OSgui.cppcheck", - "label": "Enter the file name OSgui.cppcheck" - }, - { - "do": "focus", - "name": "File name:", - "role": "text", - "label": "Focus the file name field" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the file name" - }, - { - "do": "wait", - "name": "Project file: OSgui.cppcheck", - "role": "dialog", - "timeout": 10, - "label": "Wait for the project file dialog" - }, - { - "do": "click", - "name": "Paths and Defines", - "role": "page tab", - "label": "Go to the Paths and Defines tab" - }, - { - "do": "click", - "name": "Add...", - "role": "button", - "nth": 0, - "label": "Add a path to check" - }, - { - "do": "wait", - "name": "Select a directory to check", - "role": "dialog", - "label": "Wait for the directory dialog" - }, - { - "do": "settext", - "name": "Directory:", - "role": "text", - "value": "{project:basic-project}/src", - "label": "Enter the folder src" - }, - { - "do": "click", - "name": "Choose", - "role": "button", - "label": "Choose the folder src" - }, - { - "do": "wait", - "name": "Select a directory to check", - "role": "dialog", - "gone": true, - "label": "Directory dialog closes" - }, - { - "expect": "visible", - "name": "src", - "role": "list item", - "exact": true, - "label": "src is in the paths to check" - }, - { - "do": "click", - "name": "Addons", - "role": "page tab", - "label": "Go to the Addons tab" - }, - { - "do": "click", - "name": "{addon}", - "role": "check box", - "nth": 0, - "label": "Tick the addon checkbox" - }, - { - "expect": "state", - "name": "{addon}", - "role": "check box", - "nth": 0, - "state": "checked", - "label": "The addon checkbox is ticked" - }, - { - "do": "click", - "name": "OK", - "role": "button", - "label": "Save the project file" - }, - { - "do": "wait", - "name": "Yes", - "role": "button", - "timeout": 10, - "label": "Question to create the build dir" - }, - { - "do": "click", - "name": "Yes", - "role": "button", - "label": "Create the build dir" - }, - { - "do": "wait", - "name": "src/b.c", - "role": "table cell", - "timeout": 120, - "label": "Wait for the analysis results" - }, - { - "do": "sleep", - "seconds": 3 - }, - { - "expect": "state", - "name": "Stop analysis", - "role": "button", - "not_state": "enabled", - "timeout": 120, - "label": "The analysis is done" - }, - { - "do": "focus", - "name": "src/a.c", - "role": "table cell", - "label": "Select the results of a.c" - }, - { - "do": "key", - "keys": [ - "asterisk", - "End", - "asterisk" - ], - "label": "Expand the results of all files" - }, - { - "do": "sleep", - "seconds": 1 - }, - { - "expect": "file", - "path": "{project:basic-project}/OSgui.cppcheck", - "contains": "{addon}", - "label": "Project file has the addon entry" - }, - { - "expect": "file", - "path": "{project:basic-project}/OSgui.cppcheck", - "contains": "", - "label": "Project file has the path src" - }, - { - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "label": "Analysis results are shown" - }, - { - "expect": "text", - "not_contains": "internalError", - "label": "No internalError about the addon in the results" - } - ], - "script_for": "716caa8a0f55", - "matrix": { - "addon": [ - "misra", - "y2038", - "thread" - ] - } - }, - { - "name": "Add the Misra addon to a project", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Open File > Edit Project File.", - "On the Addons tab select MISRA C 2012 and save the project file.", - "Wait until the analysis is done." - ], - "expected": "basic.cppcheck now has the addon misra in it and the analysis is run again.", - "id": "tf6da2cfd", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the file dialog" - }, - { - "do": "wait", - "name": "Checking src/b.c", - "timeout": 20, - "label": "Wait for the first analysis to run" - }, - { - "do": "sleep", - "seconds": 3, - "label": "Let the first analysis finish" - }, - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Edit Project File", - "role": "menu item", - "label": "Choose Edit Project File" - }, - { - "expect": "visible", - "name": "Project file: basic.cppcheck", - "role": "dialog", - "label": "Project file dialog is open" - }, - { - "do": "click", - "name": "Addons", - "role": "page tab", - "label": "Go to the Addons tab" - }, - { - "do": "click", - "name": "Misra C 2012", - "role": "check box", - "label": "Select MISRA C 2012" - }, - { - "expect": "state", - "name": "Misra C 2012", - "state": "checked", - "label": "MISRA C 2012 is checked" - }, - { - "do": "click", - "name": "OK", - "role": "button", - "label": "Save the project file" - }, - { - "expect": "gone", - "name": "Project file: basic.cppcheck", - "label": "Project file dialog is closed" - }, - { - "do": "sleep", - "seconds": 4, - "label": "Wait for the second analysis to finish" - }, - { - "expect": "file", - "path": "{project:basic-project}/basic.cppcheck", - "contains": "misra", - "label": "Project file has the misra addon" - }, - { - "expect": "visible", - "name": "Checking src/b.c", - "role": "list item", - "nth": 1, - "label": "The analysis was run again" - } - ], - "script_for": "351823845d96" - } - ] -} diff --git a/gui/manualtest/tests/projects.scripts.json b/gui/manualtest/tests/projects.scripts.json new file mode 100644 index 00000000000..a78d2f6b793 --- /dev/null +++ b/gui/manualtest/tests/projects.scripts.json @@ -0,0 +1,771 @@ +{ + "tf9fc8bf6": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File in the File menu" + }, + { + "expect": "visible", + "name": "Select Project File", + "role": "dialog", + "label": "The project file dialog is shown" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the dialog to open the project" + }, + { + "expect": "visible", + "name": "Project: basic.cppcheck", + "role": "frame", + "timeout": 15, + "label": "The title of the main window names basic.cppcheck" + }, + { + "do": "wait", + "name": "Select Project File", + "gone": true, + "label": "The dialog is closed" + }, + { + "do": "sleep", + "seconds": 4, + "label": "Wait for the analysis to finish" + }, + { + "do": "focus", + "role": "tree", + "label": "Focus the results tree" + }, + { + "do": "key", + "keys": [ + "Home", + "Down", + "Down", + "Right", + "Up", + "Right", + "Up", + "Right", + "Home" + ], + "label": "Expand the results of all three files and go to the top" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "timeout": 10, + "label": "nullPointer errors are shown for util.h and a.c" + }, + { + "expect": "text", + "contains": "src/a.c", + "label": "Results for a.c are shown" + }, + { + "expect": "text", + "contains": "include/util.h", + "label": "Results for util.h are shown" + }, + { + "do": "key", + "keys": [ + "End" + ], + "label": "Scroll to the results of b.c" + }, + { + "expect": "visible", + "name": "arrayIndexOutOfBounds", + "role": "table cell", + "timeout": 10, + "label": "arrayIndexOutOfBounds error is shown for b.c" + } + ], + "script_for": "7128457516b3" + }, + "t6bb11424": { + "script": [ + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the first project" + }, + { + "do": "type", + "value": "{project:switch-project}/one/p1.cppcheck", + "label": "Enter path of p1.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "Project: p1.cppcheck", + "role": "frame", + "timeout": 10, + "label": "First project is loaded" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 20, + "label": "Results of the first project show a.c" + }, + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the second project" + }, + { + "do": "type", + "value": "{project:switch-project}/two/p2.cppcheck", + "label": "Enter path of p2.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "Project: p2.cppcheck", + "role": "frame", + "timeout": 10, + "label": "Second project is loaded" + }, + { + "expect": "visible", + "name": "b.c", + "role": "table cell", + "timeout": 20, + "label": "Results of the second project show b.c" + }, + { + "expect": "gone", + "name": "a.c", + "role": "table cell", + "label": "Results of the first project are gone" + } + ], + "script_for": "70097c80a6ed" + }, + "t176fd17f": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:switch-project}/one/p1.cppcheck", + "label": "Enter the path of p1.cppcheck" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Open p1.cppcheck" + }, + { + "expect": "text", + "contains": "Project: p1.cppcheck", + "timeout": 10, + "label": "Project p1 is open" + }, + { + "expect": "visible", + "name": "Checking a.c", + "timeout": 15, + "label": "Analysis of p1 is done" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:switch-project}/two/p2.cppcheck", + "label": "Enter the path of p2.cppcheck" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Open p2.cppcheck" + }, + { + "expect": "text", + "contains": "Project: p2.cppcheck", + "timeout": 10, + "label": "Project p2 is open" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Wait until the analysis of p2 is done" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "p1.cppcheck", + "role": "menu item", + "label": "Click p1.cppcheck in the recent projects list" + }, + { + "expect": "text", + "contains": "Project: p1.cppcheck", + "timeout": 10, + "label": "Main window title names p1.cppcheck" + }, + { + "expect": "text", + "not_contains": "Project: p2.cppcheck", + "label": "Title does not name p2.cppcheck" + } + ], + "script_for": "2ea42621479d" + }, + "t1e899c1c": { + "script": [ + { + "label": "Open File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Open File > New Project File", + "do": "click", + "name": "New Project File", + "role": "menu item" + }, + { + "label": "Wait for the file name dialog", + "do": "wait", + "name": "Select Project Filename", + "role": "dialog" + }, + { + "label": "Enter the file name OSgui.cppcheck", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/OSgui.cppcheck" + }, + { + "label": "Confirm the file name", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "Project file dialog is shown", + "expect": "visible", + "name": "Project file: OSgui.cppcheck", + "role": "dialog" + }, + { + "label": "Add a path to check", + "do": "click", + "name": "Add...", + "role": "button", + "nth": 0 + }, + { + "label": "Wait for the directory dialog", + "do": "wait", + "name": "Select a directory to check", + "role": "dialog" + }, + { + "label": "Enter the src folder", + "do": "settext", + "name": "Directory:", + "role": "text", + "value": "{project:basic-project}/src" + }, + { + "label": "Choose the src folder", + "do": "click", + "name": "Choose", + "role": "button" + }, + { + "label": "src is in the paths list", + "expect": "visible", + "name": "src", + "role": "list item" + }, + { + "label": "Save the project file without selecting an addon", + "do": "click", + "name": "OK", + "role": "button" + }, + { + "label": "Create the build dir when asked", + "do": "click", + "name": "Yes", + "role": "button" + }, + { + "label": "Wait until the analysis has checked b.c", + "expect": "text", + "contains": "Checking src/b.c", + "timeout": 30 + }, + { + "label": "Wait for the results of a.c", + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30 + }, + { + "label": "Select a.c results", + "do": "click", + "name": "src/a.c", + "role": "table cell", + "nth": 0, + "mouse": true + }, + { + "label": "Collapse a.c results to get a known state", + "do": "key", + "keys": [ + "Left" + ] + }, + { + "label": "Expand a.c results", + "do": "key", + "keys": [ + "Right" + ] + }, + { + "label": "nullPointer is reported in a.c", + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "timeout": 10 + }, + { + "label": "Select b.c results", + "do": "click", + "name": "src/b.c", + "role": "table cell", + "nth": 0, + "mouse": true + }, + { + "label": "Collapse b.c results to get a known state", + "do": "key", + "keys": [ + "Left" + ] + }, + { + "label": "Expand b.c results", + "do": "key", + "keys": [ + "Right" + ] + }, + { + "label": "arrayIndexOutOfBounds is reported in b.c", + "expect": "visible", + "name": "arrayIndexOutOfBounds", + "role": "table cell", + "timeout": 10 + }, + { + "label": "util.h is not checked", + "expect": "text", + "not_contains": "Checking include/util.h" + }, + { + "label": "Project file contains the src path", + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "contains": "" + }, + { + "label": "Project file has no addon", + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "not_contains": "addon" + }, + { + "label": "Project file has no include path", + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "not_contains": "include" + } + ], + "script_for": "b3616d866fea" + }, + "t202b9515": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "New Project File...", + "role": "menu item", + "label": "Open File > New Project File" + }, + { + "do": "wait", + "name": "Select Project Filename", + "role": "dialog", + "label": "Wait for the file name dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/OSgui.cppcheck", + "label": "Enter the file name OSgui.cppcheck" + }, + { + "do": "focus", + "name": "File name:", + "role": "text", + "label": "Focus the file name field" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file name" + }, + { + "do": "wait", + "name": "Project file: OSgui.cppcheck", + "role": "dialog", + "timeout": 10, + "label": "Wait for the project file dialog" + }, + { + "do": "click", + "name": "Paths and Defines", + "role": "page tab", + "label": "Go to the Paths and Defines tab" + }, + { + "do": "click", + "name": "Add...", + "role": "button", + "nth": 0, + "label": "Add a path to check" + }, + { + "do": "wait", + "name": "Select a directory to check", + "role": "dialog", + "label": "Wait for the directory dialog" + }, + { + "do": "settext", + "name": "Directory:", + "role": "text", + "value": "{project:basic-project}/src", + "label": "Enter the folder src" + }, + { + "do": "click", + "name": "Choose", + "role": "button", + "label": "Choose the folder src" + }, + { + "do": "wait", + "name": "Select a directory to check", + "role": "dialog", + "gone": true, + "label": "Directory dialog closes" + }, + { + "expect": "visible", + "name": "src", + "role": "list item", + "exact": true, + "label": "src is in the paths to check" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "do": "click", + "name": "{addon}", + "role": "check box", + "nth": 0, + "label": "Tick the addon checkbox" + }, + { + "expect": "state", + "name": "{addon}", + "role": "check box", + "nth": 0, + "state": "checked", + "label": "The addon checkbox is ticked" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Save the project file" + }, + { + "do": "wait", + "name": "Yes", + "role": "button", + "timeout": 10, + "label": "Question to create the build dir" + }, + { + "do": "click", + "name": "Yes", + "role": "button", + "label": "Create the build dir" + }, + { + "do": "wait", + "name": "src/b.c", + "role": "table cell", + "timeout": 120, + "label": "Wait for the analysis results" + }, + { + "do": "sleep", + "seconds": 3 + }, + { + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 120, + "label": "The analysis is done" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "Select the results of a.c" + }, + { + "do": "key", + "keys": [ + "asterisk", + "End", + "asterisk" + ], + "label": "Expand the results of all files" + }, + { + "do": "sleep", + "seconds": 1 + }, + { + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "contains": "{addon}", + "label": "Project file has the addon entry" + }, + { + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "contains": "", + "label": "Project file has the path src" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "Analysis results are shown" + }, + { + "expect": "text", + "not_contains": "internalError", + "label": "No internalError about the addon in the results" + } + ], + "script_for": "60cd34c48191" + }, + "tf6da2cfd": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "Checking src/b.c", + "timeout": 20, + "label": "Wait for the first analysis to run" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the first analysis finish" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Edit Project File", + "role": "menu item", + "label": "Choose Edit Project File" + }, + { + "expect": "visible", + "name": "Project file: basic.cppcheck", + "role": "dialog", + "label": "Project file dialog is open" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "do": "click", + "name": "Misra C 2012", + "role": "check box", + "label": "Select MISRA C 2012" + }, + { + "expect": "state", + "name": "Misra C 2012", + "state": "checked", + "label": "MISRA C 2012 is checked" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Save the project file" + }, + { + "expect": "gone", + "name": "Project file: basic.cppcheck", + "label": "Project file dialog is closed" + }, + { + "do": "sleep", + "seconds": 4, + "label": "Wait for the second analysis to finish" + }, + { + "expect": "file", + "path": "{project:basic-project}/basic.cppcheck", + "contains": "misra", + "label": "Project file has the misra addon" + }, + { + "expect": "visible", + "name": "Checking src/b.c", + "role": "list item", + "nth": 1, + "label": "The analysis was run again" + } + ], + "script_for": "452f351f0fb2" + } +} diff --git a/gui/manualtest/tests/result_list.feature b/gui/manualtest/tests/result_list.feature new file mode 100644 index 00000000000..7b20fadc61f --- /dev/null +++ b/gui/manualtest/tests/result_list.feature @@ -0,0 +1,90 @@ +Feature: Results list + + Background: + Given the application is started with no project loaded + + @id:t852206f1 + Scenario: Severity buttons toggle + When I click each of the toolbar buttons Error, Warning, Style, Portability, Performance, Information, Cppcheck and Clang once + Then every button can be clicked and changes between checked and unchecked + When I click each of them once more + Then all buttons are in the state they had at the start + + @id:td7decf1a @project:basic-project + Scenario: Filter the results + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I type "arrayIndex" in the Filter field + Then only the arrayIndexOutOfBounds error in b.c is shown + When I clear the Filter field + Then all errors are shown again + + @id:t2af9d498 @project:basic-project + Scenario: Expand all + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I open View > Collapse All + And I open View > Expand All + Then all files are expanded and show their errors + And the errors in a.c, util.h and b.c are visible + + @id:tec898362 @project:basic-project + Scenario: Collapse all + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I open View > Expand All + And I open View > Collapse All + Then all files are collapsed + And the file entries are shown and their errors are not + + @id:t4c027507 @project:basic-project + Scenario: Context menu of a file entry + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I right click on the file a.c in the results (not on one of its errors) + Then the context menu of the file entry has the entries Recheck and Open containing folder + But it has none of the entries Hide, Hide all with ID or Suppress selected id, they only apply to a single error + + @id:taacc3841 @project:basic-project + Scenario: Hide one error + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I expand a.c in the results + And I right click on the first nullPointer error (line 6) and click Hide + Then that error is gone + And the other errors are still shown, including the second nullPointer error in a.c (line 12) + + @id:t2937b690 @project:basic-project + Scenario: Hide all errors with the same ID + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I expand a.c in the results + And I right click on a nullPointer error and click Hide all with ID + Then all nullPointer errors are gone, in a.c and in util.h + And the arrayIndexOutOfBounds error in b.c is still shown + + @id:t313463d3 @project:basic-project + Scenario: Show hidden errors again + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I expand a.c in the results + And I right click on the first nullPointer error and click Hide + And I open View > Show Hidden + Then the error that was hidden is shown again + + @id:t90bf32e9 @project:basic-project + Scenario: Suppress selected ID + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I expand a.c in the results + And I right click on a nullPointer error and click Suppress selected id + And I open File > Edit Project File and go to Warning options + Then no nullPointer error is shown in the results + And the id nullPointer is listed under Suppressions in the project file dialog + + @id:t389a5f3a @project:basic-project + Scenario: Clear results + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I click Clear results + Then the results are cleared: the result tree is empty diff --git a/gui/manualtest/tests/result_list.json b/gui/manualtest/tests/result_list.json deleted file mode 100644 index 4bdebe49408..00000000000 --- a/gui/manualtest/tests/result_list.json +++ /dev/null @@ -1,1198 +0,0 @@ -{ - "title": "Results list", - "tests": [ - { - "name": "Severity buttons toggle", - "steps": [ - "Start the application with no project loaded.", - "Click each of the toolbar buttons Error, Warning, Style, Portability, Performance, Information, Cppcheck and Clang once.", - "Click each of them once more." - ], - "expected": "Every button can be clicked and changes between checked and unchecked. After the second click all buttons are in the state they had at the start.", - "id": "t852206f1", - "script": [ - { - "do": "click", - "name": "Show errors", - "role": "check box", - "label": "Click Error once" - }, - { - "do": "click", - "name": "Show warnings", - "role": "check box", - "label": "Click Warning once" - }, - { - "do": "click", - "name": "Show style warnings", - "role": "check box", - "label": "Click Style once" - }, - { - "do": "click", - "name": "Show portability", - "role": "check box", - "label": "Click Portability once" - }, - { - "do": "click", - "name": "Show performance warnings", - "role": "check box", - "label": "Click Performance once" - }, - { - "do": "click", - "name": "Show information", - "role": "check box", - "label": "Click Information once" - }, - { - "do": "click", - "name": "Cppcheck", - "role": "check box", - "label": "Click Cppcheck once" - }, - { - "do": "click", - "name": "Clang", - "role": "check box", - "label": "Click Clang once" - }, - { - "expect": "state", - "name": "Show errors", - "role": "check box", - "not_state": "checked", - "label": "Error changed to unchecked after first click" - }, - { - "expect": "state", - "name": "Show warnings", - "role": "check box", - "not_state": "checked", - "label": "Warning changed to unchecked after first click" - }, - { - "expect": "state", - "name": "Show style warnings", - "role": "check box", - "not_state": "checked", - "label": "Style changed to unchecked after first click" - }, - { - "expect": "state", - "name": "Show portability", - "role": "check box", - "not_state": "checked", - "label": "Portability changed to unchecked after first click" - }, - { - "expect": "state", - "name": "Show performance warnings", - "role": "check box", - "not_state": "checked", - "label": "Performance changed to unchecked after first click" - }, - { - "expect": "state", - "name": "Show information", - "role": "check box", - "not_state": "checked", - "label": "Information changed to unchecked after first click" - }, - { - "expect": "state", - "name": "Cppcheck", - "role": "check box", - "not_state": "checked", - "label": "Cppcheck changed to unchecked after first click" - }, - { - "expect": "state", - "name": "Clang", - "role": "check box", - "not_state": "checked", - "label": "Clang changed to unchecked after first click" - }, - { - "do": "click", - "name": "Show errors", - "role": "check box", - "label": "Click Error a second time" - }, - { - "do": "click", - "name": "Show warnings", - "role": "check box", - "label": "Click Warning a second time" - }, - { - "do": "click", - "name": "Show style warnings", - "role": "check box", - "label": "Click Style a second time" - }, - { - "do": "click", - "name": "Show portability", - "role": "check box", - "label": "Click Portability a second time" - }, - { - "do": "click", - "name": "Show performance warnings", - "role": "check box", - "label": "Click Performance a second time" - }, - { - "do": "click", - "name": "Show information", - "role": "check box", - "label": "Click Information a second time" - }, - { - "do": "click", - "name": "Cppcheck", - "role": "check box", - "label": "Click Cppcheck a second time" - }, - { - "do": "click", - "name": "Clang", - "role": "check box", - "label": "Click Clang a second time" - }, - { - "expect": "state", - "name": "Show errors", - "role": "check box", - "state": "checked", - "label": "Error is back to its start state (checked)" - }, - { - "expect": "state", - "name": "Show warnings", - "role": "check box", - "state": "checked", - "label": "Warning is back to its start state (checked)" - }, - { - "expect": "state", - "name": "Show style warnings", - "role": "check box", - "state": "checked", - "label": "Style is back to its start state (checked)" - }, - { - "expect": "state", - "name": "Show portability", - "role": "check box", - "state": "checked", - "label": "Portability is back to its start state (checked)" - }, - { - "expect": "state", - "name": "Show performance warnings", - "role": "check box", - "state": "checked", - "label": "Performance is back to its start state (checked)" - }, - { - "expect": "state", - "name": "Show information", - "role": "check box", - "state": "checked", - "label": "Information is back to its start state (checked)" - }, - { - "expect": "state", - "name": "Cppcheck", - "role": "check box", - "state": "checked", - "label": "Cppcheck is back to its start state (checked)" - }, - { - "expect": "state", - "name": "Clang", - "role": "check box", - "state": "checked", - "label": "Clang is back to its start state (checked)" - } - ], - "script_for": "6a0b05c5f781" - }, - { - "name": "Filter the results", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Type \"arrayIndex\" in the Filter field.", - "Clear the Filter field." - ], - "expected": "While the filter is \"arrayIndex\" only the arrayIndexOutOfBounds error in b.c is shown. After clearing it all errors are shown again.", - "id": "td7decf1a", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "click", - "name": "Open", - "role": "button", - "exact": true, - "label": "Confirm the file dialog" - }, - { - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "timeout": 30, - "label": "Analysis is done and results are shown" - }, - { - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "label": "All files are listed before filtering" - }, - { - "expect": "visible", - "name": "include/util.h", - "role": "table cell", - "label": "util.h is listed before filtering" - }, - { - "do": "focus", - "path": "0.4.1", - "role": "text", - "label": "Focus the Filter field" - }, - { - "do": "type", - "value": "arrayIndex", - "label": "Type arrayIndex in the Filter field" - }, - { - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "label": "Only the b.c error remains shown" - }, - { - "expect": "gone", - "name": "src/a.c", - "role": "table cell", - "label": "a.c error is filtered out" - }, - { - "expect": "gone", - "name": "include/util.h", - "role": "table cell", - "label": "util.h error is filtered out" - }, - { - "do": "settext", - "path": "0.4.1", - "role": "text", - "value": "", - "label": "Clear the Filter field" - }, - { - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "label": "a.c error is shown again" - }, - { - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "label": "b.c error is shown again" - }, - { - "expect": "visible", - "name": "include/util.h", - "role": "table cell", - "label": "util.h error is shown again" - } - ], - "script_for": "f8d406828551" - }, - { - "name": "Expand all", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Open View > Collapse All and then View > Expand All." - ], - "expected": "All files are expanded and show their errors: the errors in a.c, util.h and b.c are visible.", - "id": "t2af9d498", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the file dialog" - }, - { - "do": "wait", - "name": "Checking src/b.c", - "role": "list item", - "timeout": 30, - "label": "Wait until the analysis has run" - }, - { - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "timeout": 30, - "label": "Results are shown" - }, - { - "do": "click", - "name": "View", - "role": "menu item", - "label": "Open the View menu" - }, - { - "do": "click", - "name": "Collapse all", - "role": "menu item", - "label": "View > Collapse All" - }, - { - "do": "click", - "name": "Expand all", - "role": "menu item", - "label": "View > Expand All (menu is still open)" - }, - { - "expect": "visible", - "name": "Null pointer dereference: r", - "role": "table cell", - "label": "The error in util.h is visible" - }, - { - "expect": "visible", - "name": "Null pointer dereference: p", - "role": "table cell", - "label": "The error in a.c is visible" - }, - { - "expect": "state", - "name": "include/util.h", - "role": "table cell", - "state": "expanded", - "nth": 0, - "label": "util.h is expanded" - }, - { - "expect": "state", - "name": "src/a.c", - "role": "table cell", - "state": "expanded", - "nth": 0, - "label": "a.c is expanded" - } - ], - "script_for": "e38cfdd0677d" - }, - { - "name": "Collapse all", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Open View > Expand All and then View > Collapse All." - ], - "expected": "All files are collapsed: the file entries are shown and their errors are not.", - "id": "tec898362", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "wait", - "name": "Select Project File", - "role": "dialog", - "label": "File dialog appears" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the file dialog" - }, - { - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "timeout": 60, - "label": "Analysis is done and the file entries are shown" - }, - { - "do": "click", - "name": "View", - "role": "menu item", - "label": "Open the View menu" - }, - { - "do": "click", - "name": "Expand all", - "role": "menu item", - "label": "View > Expand All" - }, - { - "expect": "visible", - "name": "nullPointer", - "role": "table cell", - "label": "Errors are visible after expanding" - }, - { - "do": "hover", - "name": "View", - "role": "menu item", - "label": "Open the View menu again" - }, - { - "do": "click", - "name": "Collapse all", - "role": "menu item", - "label": "View > Collapse All" - }, - { - "expect": "visible", - "name": "include/util.h", - "role": "table cell", - "label": "File entry util.h is shown" - }, - { - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "label": "File entry a.c is shown" - }, - { - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "label": "File entry b.c is shown" - }, - { - "expect": "gone", - "name": "nullPointer", - "label": "Errors are not shown" - }, - { - "expect": "gone", - "name": "constVariablePointer", - "label": "Style errors are not shown" - } - ], - "script_for": "fc83a8d2b4c1" - }, - { - "name": "Context menu of a file entry", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Right click on the file a.c in the results (not on one of its errors)." - ], - "expected": "The context menu of the file entry has the entries Recheck and Open containing folder. It has none of the entries Hide, Hide all with ID or Suppress selected id, they only apply to a single error.", - "id": "t4c027507", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File from the File menu" - }, - { - "do": "wait", - "name": "Select Project File", - "role": "dialog", - "label": "Wait for the file dialog" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the path of basic.cppcheck" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the file dialog" - }, - { - "do": "wait", - "name": "src/b.c", - "role": "table cell", - "timeout": 60, - "label": "Wait until the analysis is done and results are shown" - }, - { - "do": "wait", - "name": "Select Project File", - "gone": true, - "label": "File dialog is closed" - }, - { - "do": "rightclick", - "name": "src/a.c", - "role": "table cell", - "label": "Right click on the file a.c in the results" - }, - { - "expect": "ocr", - "contains": "Recheck", - "label": "Context menu has Recheck" - }, - { - "expect": "ocr", - "contains": "Open containing folder", - "label": "Context menu has Open containing folder" - }, - { - "expect": "ocr", - "not_contains": "Hide", - "label": "Context menu has no Hide entries" - }, - { - "expect": "ocr", - "not_contains": "Suppress selected id", - "label": "Context menu has no Suppress selected id" - } - ], - "script_for": "e2522fcc1afb" - }, - { - "name": "Hide one error", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Expand a.c in the results.", - "Right click on the first nullPointer error (line 6) and click Hide." - ], - "expected": "That error is gone. The other errors are still shown, including the second nullPointer error in a.c (line 12).", - "id": "taacc3841", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Open the project file" - }, - { - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "timeout": 30, - "label": "Analysis is done and results show a.c" - }, - { - "do": "focus", - "name": "src/a.c", - "role": "table cell", - "label": "Focus the a.c row" - }, - { - "do": "key", - "keys": [ - "Right" - ], - "label": "Expand a.c in the results" - }, - { - "expect": "visible", - "name": "Null pointer dereference: p", - "role": "table cell", - "label": "First nullPointer error (line 6) is shown" - }, - { - "do": "rightclick", - "name": "Null pointer dereference: p", - "role": "table cell", - "label": "Right click on the first nullPointer error" - }, - { - "do": "key", - "keys": [ - "Down", - "Down", - "Down", - "Return" - ], - "label": "Click Hide (Hide 1 result(s)) in the context menu" - }, - { - "expect": "gone", - "name": "Null pointer dereference: p", - "label": "The hidden error is gone" - }, - { - "expect": "visible", - "name": "Null pointer dereference: q", - "role": "table cell", - "label": "Second nullPointer error (line 12) is still shown" - }, - { - "expect": "visible", - "name": "Variable 'p' can be declared as pointer to const", - "role": "table cell", - "label": "Other errors are still shown (line 5)" - }, - { - "expect": "visible", - "name": "Variable 'q' can be declared as pointer to const", - "role": "table cell", - "label": "Other errors are still shown (line 11)" - } - ], - "script_for": "f2771f5855a0" - }, - { - "name": "Hide all errors with the same ID", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Expand a.c in the results.", - "Right click on a nullPointer error and click Hide all with ID." - ], - "expected": "All nullPointer errors are gone, in a.c and in util.h. The arrayIndexOutOfBounds error in b.c is still shown.", - "id": "t2937b690", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Open the project file" - }, - { - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "timeout": 30, - "label": "Wait until the analysis is done and results are shown" - }, - { - "do": "doubleclick", - "name": "src/a.c", - "role": "table cell", - "label": "Select a.c in the results" - }, - { - "do": "key", - "keys": [ - "Right" - ], - "label": "Expand a.c in the results" - }, - { - "expect": "visible", - "name": "nullPointer", - "role": "table cell", - "label": "nullPointer errors are listed before hiding" - }, - { - "do": "rightclick", - "name": "nullPointer", - "role": "table cell", - "nth": 0, - "label": "Right click on a nullPointer error" - }, - { - "do": "key", - "keys": [ - "Down", - "Down", - "Down", - "Down" - ], - "label": "Move to Hide all with id in the context menu" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Click Hide all with ID" - }, - { - "expect": "gone", - "name": "nullPointer", - "role": "table cell", - "label": "All nullPointer errors are gone from a.c and util.h" - }, - { - "expect": "visible", - "name": "constVariablePointer", - "role": "table cell", - "label": "Other errors in a.c are still shown" - }, - { - "do": "key", - "keys": [ - "End", - "Right" - ], - "label": "Expand b.c in the results" - }, - { - "expect": "visible", - "name": "arrayIndexOutOfBounds", - "role": "table cell", - "label": "The arrayIndexOutOfBounds error in b.c is still shown" - } - ], - "script_for": "e220a9a4c618" - }, - { - "name": "Show hidden errors again", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Expand a.c in the results.", - "Right click on the first nullPointer error and click Hide.", - "Open View > Show Hidden." - ], - "expected": "The error that was hidden is shown again.", - "id": "t313463d3", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "click", - "name": "Open", - "role": "button", - "nth": 1, - "label": "Confirm the file dialog" - }, - { - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "timeout": 30, - "label": "Analysis is done and results are shown" - }, - { - "do": "sleep", - "seconds": 3, - "label": "Let the analysis finish completely" - }, - { - "do": "click", - "name": "src/a.c", - "role": "table cell", - "mouse": true, - "label": "Select a.c in the results" - }, - { - "do": "key", - "keys": [ - "Right" - ], - "label": "Expand a.c in the results" - }, - { - "expect": "visible", - "name": "Null pointer dereference: p", - "role": "table cell", - "label": "The first nullPointer error is listed" - }, - { - "do": "rightclick", - "name": "Null pointer dereference: p", - "role": "table cell", - "label": "Right click on the first nullPointer error" - }, - { - "do": "key", - "keys": [ - "Down", - "Down", - "Down", - "Return" - ], - "label": "Click Hide in the context menu" - }, - { - "expect": "gone", - "name": "Null pointer dereference: p", - "role": "table cell", - "label": "The error is hidden" - }, - { - "do": "click", - "name": "View", - "role": "menu item", - "label": "Open the View menu" - }, - { - "do": "click", - "name": "Show hidden", - "role": "menu item", - "label": "Click Show Hidden" - }, - { - "expect": "visible", - "name": "Null pointer dereference: p", - "role": "table cell", - "timeout": 5, - "observed_fail": true, - "label": "The hidden error is shown again" - } - ], - "script_for": "0a6d5d24428f" - }, - { - "name": "Suppress selected ID", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Expand a.c in the results.", - "Right click on a nullPointer error and click Suppress selected id.", - "Open File > Edit Project File and go to Warning options." - ], - "expected": "No nullPointer error is shown in the results. The id nullPointer is listed under Suppressions in the project file dialog.", - "id": "t90bf32e9", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open File menu" - }, - { - "do": "click", - "name": "Open Project File", - "role": "menu item", - "label": "Click Open Project File" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter project file path" - }, - { - "do": "click", - "name": "Open", - "role": "button", - "exact": true, - "label": "Confirm the file dialog" - }, - { - "expect": "visible", - "name": "src/a.c", - "role": "table cell", - "timeout": 60, - "label": "Wait until the analysis is done" - }, - { - "do": "sleep", - "seconds": 3, - "label": "Let the analysis finish" - }, - { - "do": "focus", - "name": "src/a.c", - "role": "table cell", - "label": "Select a.c in the results" - }, - { - "do": "key", - "keys": [ - "Right" - ], - "label": "Expand a.c in the results" - }, - { - "expect": "visible", - "name": "nullPointer", - "role": "table cell", - "label": "nullPointer error is shown under a.c" - }, - { - "do": "rightclick", - "name": "nullPointer", - "role": "table cell", - "nth": 0, - "label": "Right click on a nullPointer error" - }, - { - "do": "key", - "keys": [ - "Down", - "Down", - "Down", - "Down", - "Down", - "Return" - ], - "label": "Click Suppress selected id" - }, - { - "expect": "gone", - "name": "nullPointer", - "role": "table cell", - "label": "No nullPointer error is shown in the results" - }, - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open File menu" - }, - { - "do": "click", - "name": "Edit Project File", - "role": "menu item", - "label": "Click Edit Project File" - }, - { - "do": "click", - "name": "Warning options", - "role": "page tab", - "label": "Go to Warning options" - }, - { - "expect": "visible", - "name": "nullPointer", - "role": "list item", - "label": "The id nullPointer is listed under Suppressions" - } - ], - "script_for": "a86c68038052" - }, - { - "name": "Clear results", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Click Clear results." - ], - "expected": "The results are cleared: the result tree is empty.", - "id": "t389a5f3a", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open Project File...", - "role": "menu item", - "label": "Choose Open Project File" - }, - { - "do": "settext", - "name": "File name", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the project file path" - }, - { - "do": "click", - "name": "Open", - "role": "button", - "exact": true, - "label": "Confirm the file dialog" - }, - { - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "timeout": 30, - "label": "Analysis results are shown" - }, - { - "do": "sleep", - "seconds": 3, - "label": "Wait until the analysis is done" - }, - { - "expect": "state", - "name": "Clear results", - "role": "button", - "state": "enabled", - "label": "Analysis finished, Clear results is available" - }, - { - "do": "click", - "name": "Clear results", - "role": "button", - "label": "Click Clear results" - }, - { - "expect": "gone", - "name": "src/a.c", - "role": "table cell", - "label": "Result tree has no src/a.c entry" - }, - { - "expect": "gone", - "name": "src/b.c", - "role": "table cell", - "label": "Result tree has no src/b.c entry" - }, - { - "expect": "gone", - "name": "include/util.h", - "role": "table cell", - "label": "Result tree has no include/util.h entry" - } - ], - "script_for": "2f1832fabb0d" - } - ] -} diff --git a/gui/manualtest/tests/result_list.scripts.json b/gui/manualtest/tests/result_list.scripts.json new file mode 100644 index 00000000000..6495e860cb2 --- /dev/null +++ b/gui/manualtest/tests/result_list.scripts.json @@ -0,0 +1,1081 @@ +{ + "t852206f1": { + "script": [ + { + "do": "click", + "name": "Show errors", + "role": "check box", + "label": "Click Error once" + }, + { + "do": "click", + "name": "Show warnings", + "role": "check box", + "label": "Click Warning once" + }, + { + "do": "click", + "name": "Show style warnings", + "role": "check box", + "label": "Click Style once" + }, + { + "do": "click", + "name": "Show portability", + "role": "check box", + "label": "Click Portability once" + }, + { + "do": "click", + "name": "Show performance warnings", + "role": "check box", + "label": "Click Performance once" + }, + { + "do": "click", + "name": "Show information", + "role": "check box", + "label": "Click Information once" + }, + { + "do": "click", + "name": "Cppcheck", + "role": "check box", + "label": "Click Cppcheck once" + }, + { + "do": "click", + "name": "Clang", + "role": "check box", + "label": "Click Clang once" + }, + { + "expect": "state", + "name": "Show errors", + "role": "check box", + "not_state": "checked", + "label": "Error changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show warnings", + "role": "check box", + "not_state": "checked", + "label": "Warning changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show style warnings", + "role": "check box", + "not_state": "checked", + "label": "Style changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show portability", + "role": "check box", + "not_state": "checked", + "label": "Portability changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show performance warnings", + "role": "check box", + "not_state": "checked", + "label": "Performance changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show information", + "role": "check box", + "not_state": "checked", + "label": "Information changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Cppcheck", + "role": "check box", + "not_state": "checked", + "label": "Cppcheck changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Clang", + "role": "check box", + "not_state": "checked", + "label": "Clang changed to unchecked after first click" + }, + { + "do": "click", + "name": "Show errors", + "role": "check box", + "label": "Click Error a second time" + }, + { + "do": "click", + "name": "Show warnings", + "role": "check box", + "label": "Click Warning a second time" + }, + { + "do": "click", + "name": "Show style warnings", + "role": "check box", + "label": "Click Style a second time" + }, + { + "do": "click", + "name": "Show portability", + "role": "check box", + "label": "Click Portability a second time" + }, + { + "do": "click", + "name": "Show performance warnings", + "role": "check box", + "label": "Click Performance a second time" + }, + { + "do": "click", + "name": "Show information", + "role": "check box", + "label": "Click Information a second time" + }, + { + "do": "click", + "name": "Cppcheck", + "role": "check box", + "label": "Click Cppcheck a second time" + }, + { + "do": "click", + "name": "Clang", + "role": "check box", + "label": "Click Clang a second time" + }, + { + "expect": "state", + "name": "Show errors", + "role": "check box", + "state": "checked", + "label": "Error is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show warnings", + "role": "check box", + "state": "checked", + "label": "Warning is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show style warnings", + "role": "check box", + "state": "checked", + "label": "Style is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show portability", + "role": "check box", + "state": "checked", + "label": "Portability is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show performance warnings", + "role": "check box", + "state": "checked", + "label": "Performance is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show information", + "role": "check box", + "state": "checked", + "label": "Information is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Cppcheck", + "role": "check box", + "state": "checked", + "label": "Cppcheck is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Clang", + "role": "check box", + "state": "checked", + "label": "Clang is back to its start state (checked)" + } + ], + "script_for": "e5baac140df9" + }, + "td7decf1a": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and results are shown" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "All files are listed before filtering" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "label": "util.h is listed before filtering" + }, + { + "do": "focus", + "path": "0.4.1", + "role": "text", + "label": "Focus the Filter field" + }, + { + "do": "type", + "value": "arrayIndex", + "label": "Type arrayIndex in the Filter field" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "label": "Only the b.c error remains shown" + }, + { + "expect": "gone", + "name": "src/a.c", + "role": "table cell", + "label": "a.c error is filtered out" + }, + { + "expect": "gone", + "name": "include/util.h", + "role": "table cell", + "label": "util.h error is filtered out" + }, + { + "do": "settext", + "path": "0.4.1", + "role": "text", + "value": "", + "label": "Clear the Filter field" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "a.c error is shown again" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "label": "b.c error is shown again" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "label": "util.h error is shown again" + } + ], + "script_for": "f86b9b8205a7" + }, + "t2af9d498": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "Checking src/b.c", + "role": "list item", + "timeout": 30, + "label": "Wait until the analysis has run" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "Results are shown" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Collapse all", + "role": "menu item", + "label": "View > Collapse All" + }, + { + "do": "click", + "name": "Expand all", + "role": "menu item", + "label": "View > Expand All (menu is still open)" + }, + { + "expect": "visible", + "name": "Null pointer dereference: r", + "role": "table cell", + "label": "The error in util.h is visible" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The error in a.c is visible" + }, + { + "expect": "state", + "name": "include/util.h", + "role": "table cell", + "state": "expanded", + "nth": 0, + "label": "util.h is expanded" + }, + { + "expect": "state", + "name": "src/a.c", + "role": "table cell", + "state": "expanded", + "nth": 0, + "label": "a.c is expanded" + } + ], + "script_for": "3d1da0542440" + }, + "tec898362": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "File dialog appears" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 60, + "label": "Analysis is done and the file entries are shown" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Expand all", + "role": "menu item", + "label": "View > Expand All" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "Errors are visible after expanding" + }, + { + "do": "hover", + "name": "View", + "role": "menu item", + "label": "Open the View menu again" + }, + { + "do": "click", + "name": "Collapse all", + "role": "menu item", + "label": "View > Collapse All" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "label": "File entry util.h is shown" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "File entry a.c is shown" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "label": "File entry b.c is shown" + }, + { + "expect": "gone", + "name": "nullPointer", + "label": "Errors are not shown" + }, + { + "expect": "gone", + "name": "constVariablePointer", + "label": "Style errors are not shown" + } + ], + "script_for": "405a34af72d2" + }, + "t4c027507": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File from the File menu" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "Wait for the file dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "src/b.c", + "role": "table cell", + "timeout": 60, + "label": "Wait until the analysis is done and results are shown" + }, + { + "do": "wait", + "name": "Select Project File", + "gone": true, + "label": "File dialog is closed" + }, + { + "do": "rightclick", + "name": "src/a.c", + "role": "table cell", + "label": "Right click on the file a.c in the results" + }, + { + "expect": "ocr", + "contains": "Recheck", + "label": "Context menu has Recheck" + }, + { + "expect": "ocr", + "contains": "Open containing folder", + "label": "Context menu has Open containing folder" + }, + { + "expect": "ocr", + "not_contains": "Hide", + "label": "Context menu has no Hide entries" + }, + { + "expect": "ocr", + "not_contains": "Suppress selected id", + "label": "Context menu has no Suppress selected id" + } + ], + "script_for": "237d77caee36" + }, + "taacc3841": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and results show a.c" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "Focus the a.c row" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "First nullPointer error (line 6) is shown" + }, + { + "do": "rightclick", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "Right click on the first nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Return" + ], + "label": "Click Hide (Hide 1 result(s)) in the context menu" + }, + { + "expect": "gone", + "name": "Null pointer dereference: p", + "label": "The hidden error is gone" + }, + { + "expect": "visible", + "name": "Null pointer dereference: q", + "role": "table cell", + "label": "Second nullPointer error (line 12) is still shown" + }, + { + "expect": "visible", + "name": "Variable 'p' can be declared as pointer to const", + "role": "table cell", + "label": "Other errors are still shown (line 5)" + }, + { + "expect": "visible", + "name": "Variable 'q' can be declared as pointer to const", + "role": "table cell", + "label": "Other errors are still shown (line 11)" + } + ], + "script_for": "13f9f46bd6a3" + }, + "t2937b690": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "Wait until the analysis is done and results are shown" + }, + { + "do": "doubleclick", + "name": "src/a.c", + "role": "table cell", + "label": "Select a.c in the results" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "nullPointer errors are listed before hiding" + }, + { + "do": "rightclick", + "name": "nullPointer", + "role": "table cell", + "nth": 0, + "label": "Right click on a nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Down" + ], + "label": "Move to Hide all with id in the context menu" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Click Hide all with ID" + }, + { + "expect": "gone", + "name": "nullPointer", + "role": "table cell", + "label": "All nullPointer errors are gone from a.c and util.h" + }, + { + "expect": "visible", + "name": "constVariablePointer", + "role": "table cell", + "label": "Other errors in a.c are still shown" + }, + { + "do": "key", + "keys": [ + "End", + "Right" + ], + "label": "Expand b.c in the results" + }, + { + "expect": "visible", + "name": "arrayIndexOutOfBounds", + "role": "table cell", + "label": "The arrayIndexOutOfBounds error in b.c is still shown" + } + ], + "script_for": "0c3a81139be4" + }, + "t313463d3": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "nth": 1, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and results are shown" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the analysis finish completely" + }, + { + "do": "click", + "name": "src/a.c", + "role": "table cell", + "mouse": true, + "label": "Select a.c in the results" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The first nullPointer error is listed" + }, + { + "do": "rightclick", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "Right click on the first nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Return" + ], + "label": "Click Hide in the context menu" + }, + { + "expect": "gone", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The error is hidden" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Show hidden", + "role": "menu item", + "label": "Click Show Hidden" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "timeout": 5, + "observed_fail": true, + "label": "The hidden error is shown again" + } + ], + "script_for": "1675a0aff0c3" + }, + "t90bf32e9": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Click Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 60, + "label": "Wait until the analysis is done" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the analysis finish" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "Select a.c in the results" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "nullPointer error is shown under a.c" + }, + { + "do": "rightclick", + "name": "nullPointer", + "role": "table cell", + "nth": 0, + "label": "Right click on a nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Down", + "Down", + "Return" + ], + "label": "Click Suppress selected id" + }, + { + "expect": "gone", + "name": "nullPointer", + "role": "table cell", + "label": "No nullPointer error is shown in the results" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open File menu" + }, + { + "do": "click", + "name": "Edit Project File", + "role": "menu item", + "label": "Click Edit Project File" + }, + { + "do": "click", + "name": "Warning options", + "role": "page tab", + "label": "Go to Warning options" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "list item", + "label": "The id nullPointer is listed under Suppressions" + } + ], + "script_for": "fb50a62ef505" + }, + "t389a5f3a": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis results are shown" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Wait until the analysis is done" + }, + { + "expect": "state", + "name": "Clear results", + "role": "button", + "state": "enabled", + "label": "Analysis finished, Clear results is available" + }, + { + "do": "click", + "name": "Clear results", + "role": "button", + "label": "Click Clear results" + }, + { + "expect": "gone", + "name": "src/a.c", + "role": "table cell", + "label": "Result tree has no src/a.c entry" + }, + { + "expect": "gone", + "name": "src/b.c", + "role": "table cell", + "label": "Result tree has no src/b.c entry" + }, + { + "expect": "gone", + "name": "include/util.h", + "role": "table cell", + "label": "Result tree has no include/util.h entry" + } + ], + "script_for": "96a64734a43b" + } +} diff --git a/gui/manualtest/tests/xml_results.feature b/gui/manualtest/tests/xml_results.feature new file mode 100644 index 00000000000..c5e7af882ad --- /dev/null +++ b/gui/manualtest/tests/xml_results.feature @@ -0,0 +1,42 @@ +Feature: XML results + + Background: + Given the application is started with no project loaded + + @id:tc1fc409e @project:basic-project + Scenario Outline: Save results to a file + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I open File > Save results to file + And I choose the file type for files + And I save the results as results. in the work directory {work} + Then the file results. is saved in the format + And it has the nullPointer error in a.c ("Null pointer dereference") + And it has the arrayIndexOutOfBounds error in b.c ("out of bounds") + + Examples: + | format | + | xml | + | txt | + | csv | + + @id:tcdb051d4 @project:xml-results + Scenario: Open XML results + When I open File > Open XML + And I select {project:xml-results}/results.xml + Then the XML results are opened + And the nullPointer error in a.c is shown + + @id:t8d9a2076 @ticket:13984 @project:xml-results + Scenario: No Recheck in XML results + Given the XML results {project:xml-results}/results.xml are opened with File > Open XML + When I right click on the file a.c in the results + Then the context menu of the file has no Recheck entry and no Open containing folder entry, XML results can not be analysed again + And the application does not crash + + @id:t053b7c9c @project:xml-results @project:basic-project + Scenario: Reanalyze enabled after XML results + Given the XML results {project:xml-results}/results.xml are opened with File > Open XML + When I open the project file {project:basic-project}/basic.cppcheck + And I wait until the analysis is done + Then the button Reanalyze all files is enabled and can be clicked diff --git a/gui/manualtest/tests/xml_results.json b/gui/manualtest/tests/xml_results.json deleted file mode 100644 index e6ba4db7a59..00000000000 --- a/gui/manualtest/tests/xml_results.json +++ /dev/null @@ -1,458 +0,0 @@ -{ - "title": "XML results", - "tests": [ - { - "name": "Save results to a file", - "projects": [ - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open the project file {project:basic-project}/basic.cppcheck (File menu) and wait until the analysis is done.", - "Open File > Save results to file.", - "Choose the file type for {format} files and save the results as results.{format} in the work directory {work}." - ], - "expected": "The file results.{format} is saved in the {format} format. It has the nullPointer error in a.c (\"Null pointer dereference\") and the arrayIndexOutOfBounds error in b.c (\"out of bounds\").", - "id": "tc1fc409e", - "script": [ - { - "label": "Start the application with no project loaded", - "expect": "visible", - "name": "Cppcheck", - "role": "frame" - }, - { - "label": "Open the File menu", - "do": "click", - "name": "File", - "role": "menu item" - }, - { - "label": "Choose File > Open Project File", - "do": "click", - "name": "Open Project File...", - "role": "menu item" - }, - { - "label": "Wait for the project file dialog", - "do": "wait", - "name": "Select Project File", - "role": "dialog" - }, - { - "label": "Enter the project file basic.cppcheck", - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck" - }, - { - "label": "Open the project file", - "do": "key", - "keys": [ - "Return" - ] - }, - { - "label": "The project is loaded", - "expect": "visible", - "name": "Project: basic.cppcheck", - "role": "frame", - "timeout": 10 - }, - { - "label": "Analysis has produced results for b.c", - "expect": "visible", - "name": "src/b.c", - "role": "table cell", - "timeout": 30 - }, - { - "label": "Wait until the analysis is done", - "expect": "state", - "name": "Stop analysis", - "role": "button", - "not_state": "enabled", - "timeout": 30 - }, - { - "label": "Open the File menu", - "do": "click", - "name": "File", - "role": "menu item" - }, - { - "label": "Open File > Save results to file", - "do": "click", - "name": "Save results to file...", - "role": "menu item" - }, - { - "label": "Wait for the save dialog", - "do": "wait", - "name": "Save the report file", - "role": "dialog" - }, - { - "label": "Open the Files of type list", - "do": "click", - "name": "XML files", - "role": "combo box", - "mouse": true - }, - { - "label": "Choose the file type for {format} files", - "do": "click", - "name": "(*.{format})", - "role": "list item", - "mouse": true - }, - { - "label": "Enter results.{format} in the work directory as file name", - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{work}/results.{format}" - }, - { - "label": "Save the results", - "do": "click", - "name": "Save", - "role": "button", - "exact": true - }, - { - "label": "The save dialog is closed", - "expect": "gone", - "name": "Save the report file", - "role": "dialog" - }, - { - "label": "results.{format} is saved and names a.c", - "expect": "file", - "path": "{work}/results.{format}", - "contains": "src/a.c" - }, - { - "label": "results.{format} has the nullPointer error in a.c (Null pointer dereference)", - "expect": "file", - "path": "{work}/results.{format}", - "contains": "Null pointer dereference" - }, - { - "label": "results.{format} has the arrayIndexOutOfBounds error in b.c (out of bounds)", - "expect": "file", - "path": "{work}/results.{format}", - "regex": "src/b.c[\\s\\S]*out of bounds|out of bounds[\\s\\S]*src/b.c" - } - ], - "script_for": "d95f3ebb3c39", - "matrix": { - "format": [ - "xml", - "txt", - "csv" - ] - } - }, - { - "name": "Open XML results", - "projects": [ - "xml-results" - ], - "steps": [ - "Start the application with no project loaded.", - "Open File > Open XML.", - "Select {project:xml-results}/results.xml." - ], - "expected": "The XML results are opened: the nullPointer error in a.c is shown.", - "id": "tcdb051d4", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open XML", - "role": "menu item", - "label": "Choose File > Open XML" - }, - { - "expect": "visible", - "name": "Open the report file", - "role": "dialog", - "label": "The file dialog is shown" - }, - { - "do": "settext", - "name": "File name", - "role": "text", - "value": "{project:xml-results}/results.xml", - "label": "Select results.xml of the xml-results project" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the file dialog" - }, - { - "do": "wait", - "name": "Open the report file", - "gone": true, - "label": "The file dialog closes" - }, - { - "expect": "visible", - "name": "a.c", - "role": "table cell", - "label": "The file a.c is listed in the results" - }, - { - "do": "focus", - "name": "a.c", - "role": "table cell", - "label": "Give keyboard focus to the a.c entry" - }, - { - "do": "key", - "keys": [ - "Right" - ], - "label": "Expand the a.c entry to see its error" - }, - { - "expect": "visible", - "name": "nullPointer", - "role": "table cell", - "label": "The nullPointer error is shown" - }, - { - "expect": "visible", - "name": "Null pointer dereference: p", - "role": "table cell", - "label": "The nullPointer summary is shown" - }, - { - "expect": "visible", - "name": "error", - "role": "table cell", - "label": "The severity of the error is shown" - } - ], - "script_for": "166851745b45" - }, - { - "name": "No Recheck in XML results", - "ticket": "#13984", - "projects": [ - "xml-results" - ], - "steps": [ - "Start the application with no project loaded.", - "Open File > Open XML and select {project:xml-results}/results.xml.", - "Right click on the file a.c in the results." - ], - "expected": "The context menu of the file has no Recheck entry and no Open containing folder entry, XML results can not be analysed again. The application does not crash.", - "id": "t8d9a2076", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open XML", - "role": "menu item", - "label": "Choose File > Open XML" - }, - { - "do": "wait", - "name": "Open the report file", - "timeout": 10, - "label": "Wait for the open dialog" - }, - { - "do": "settext", - "name": "File name", - "role": "text", - "value": "{project:xml-results}/results.xml", - "label": "Enter the path of results.xml" - }, - { - "do": "click", - "name": "Open", - "role": "button", - "nth": 1, - "label": "Confirm the file dialog" - }, - { - "expect": "visible", - "name": "a.c", - "role": "table cell", - "timeout": 10, - "label": "The results show the file a.c" - }, - { - "do": "rightclick", - "name": "a.c", - "role": "table cell", - "label": "Right click on the file a.c in the results" - }, - { - "do": "key", - "keys": [ - "Menu" - ], - "label": "Make sure the context menu is open" - }, - { - "expect": "gone", - "name": "Recheck", - "label": "The context menu has no Recheck entry" - }, - { - "expect": "gone", - "name": "Open containing folder", - "label": "The context menu has no Open containing folder entry" - }, - { - "expect": "alive", - "label": "The application does not crash" - } - ], - "script_for": "dd4444ebf37c" - }, - { - "name": "Reanalyze enabled after XML results", - "projects": [ - "xml-results", - "basic-project" - ], - "steps": [ - "Start the application with no project loaded.", - "Open File > Open XML and select {project:xml-results}/results.xml.", - "Open the project file {project:basic-project}/basic.cppcheck and wait until the analysis is done." - ], - "expected": "The button Reanalyze all files is enabled and can be clicked.", - "id": "t053b7c9c", - "script": [ - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu" - }, - { - "do": "click", - "name": "Open XML", - "role": "menu item", - "label": "Choose File > Open XML" - }, - { - "do": "wait", - "name": "Open the report file", - "timeout": 10, - "label": "Wait for the Open XML dialog" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:xml-results}/results.xml", - "label": "Enter the path of results.xml" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Confirm the XML file" - }, - { - "do": "wait", - "name": "Open the report file", - "gone": true, - "timeout": 10, - "label": "Wait for the dialog to close" - }, - { - "do": "click", - "name": "File", - "role": "menu item", - "label": "Open the File menu again" - }, - { - "do": "click", - "name": "Open Project File", - "role": "menu item", - "label": "Choose File > Open Project File" - }, - { - "do": "wait", - "name": "Select Project File", - "timeout": 10, - "label": "Wait for the project file dialog" - }, - { - "do": "settext", - "name": "File name:", - "role": "text", - "value": "{project:basic-project}/basic.cppcheck", - "label": "Enter the path of basic.cppcheck" - }, - { - "do": "key", - "keys": [ - "Return" - ], - "label": "Open the project file" - }, - { - "do": "wait", - "name": "Select Project File", - "gone": true, - "timeout": 10, - "label": "Wait for the dialog to close" - }, - { - "do": "sleep", - "seconds": 5, - "label": "Wait until the analysis is done" - }, - { - "expect": "visible", - "name": "Reanalyze all files", - "role": "button", - "timeout": 10, - "label": "Reanalyze all files button is shown" - }, - { - "expect": "state", - "name": "Reanalyze all files", - "role": "button", - "state": "enabled", - "timeout": 10, - "label": "Reanalyze all files button is enabled" - }, - { - "do": "click", - "name": "Reanalyze all files", - "role": "button", - "label": "Click Reanalyze all files" - }, - { - "expect": "alive", - "label": "Application still running after the click" - } - ], - "script_for": "c34fa36b12bd" - } - ] -} diff --git a/gui/manualtest/tests/xml_results.scripts.json b/gui/manualtest/tests/xml_results.scripts.json new file mode 100644 index 00000000000..d4e9f6cc552 --- /dev/null +++ b/gui/manualtest/tests/xml_results.scripts.json @@ -0,0 +1,401 @@ +{ + "tc1fc409e": { + "script": [ + { + "label": "Start the application with no project loaded", + "expect": "visible", + "name": "Cppcheck", + "role": "frame" + }, + { + "label": "Open the File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Choose File > Open Project File", + "do": "click", + "name": "Open Project File...", + "role": "menu item" + }, + { + "label": "Wait for the project file dialog", + "do": "wait", + "name": "Select Project File", + "role": "dialog" + }, + { + "label": "Enter the project file basic.cppcheck", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck" + }, + { + "label": "Open the project file", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "The project is loaded", + "expect": "visible", + "name": "Project: basic.cppcheck", + "role": "frame", + "timeout": 10 + }, + { + "label": "Analysis has produced results for b.c", + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30 + }, + { + "label": "Wait until the analysis is done", + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 30 + }, + { + "label": "Open the File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Open File > Save results to file", + "do": "click", + "name": "Save results to file...", + "role": "menu item" + }, + { + "label": "Wait for the save dialog", + "do": "wait", + "name": "Save the report file", + "role": "dialog" + }, + { + "label": "Open the Files of type list", + "do": "click", + "name": "XML files", + "role": "combo box", + "mouse": true + }, + { + "label": "Choose the file type for {format} files", + "do": "click", + "name": "(*.{format})", + "role": "list item", + "mouse": true + }, + { + "label": "Enter results.{format} in the work directory as file name", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{work}/results.{format}" + }, + { + "label": "Save the results", + "do": "click", + "name": "Save", + "role": "button", + "exact": true + }, + { + "label": "The save dialog is closed", + "expect": "gone", + "name": "Save the report file", + "role": "dialog" + }, + { + "label": "results.{format} is saved and names a.c", + "expect": "file", + "path": "{work}/results.{format}", + "contains": "src/a.c" + }, + { + "label": "results.{format} has the nullPointer error in a.c (Null pointer dereference)", + "expect": "file", + "path": "{work}/results.{format}", + "contains": "Null pointer dereference" + }, + { + "label": "results.{format} has the arrayIndexOutOfBounds error in b.c (out of bounds)", + "expect": "file", + "path": "{work}/results.{format}", + "regex": "src/b.c[\\s\\S]*out of bounds|out of bounds[\\s\\S]*src/b.c" + } + ], + "script_for": "dad1485bb95a" + }, + "tcdb051d4": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open XML", + "role": "menu item", + "label": "Choose File > Open XML" + }, + { + "expect": "visible", + "name": "Open the report file", + "role": "dialog", + "label": "The file dialog is shown" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:xml-results}/results.xml", + "label": "Select results.xml of the xml-results project" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "Open the report file", + "gone": true, + "label": "The file dialog closes" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "label": "The file a.c is listed in the results" + }, + { + "do": "focus", + "name": "a.c", + "role": "table cell", + "label": "Give keyboard focus to the a.c entry" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand the a.c entry to see its error" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "The nullPointer error is shown" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The nullPointer summary is shown" + }, + { + "expect": "visible", + "name": "error", + "role": "table cell", + "label": "The severity of the error is shown" + } + ], + "script_for": "35589d1420ec" + }, + "t8d9a2076": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open XML", + "role": "menu item", + "label": "Choose File > Open XML" + }, + { + "do": "wait", + "name": "Open the report file", + "timeout": 10, + "label": "Wait for the open dialog" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:xml-results}/results.xml", + "label": "Enter the path of results.xml" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "nth": 1, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 10, + "label": "The results show the file a.c" + }, + { + "do": "rightclick", + "name": "a.c", + "role": "table cell", + "label": "Right click on the file a.c in the results" + }, + { + "do": "key", + "keys": [ + "Menu" + ], + "label": "Make sure the context menu is open" + }, + { + "expect": "gone", + "name": "Recheck", + "label": "The context menu has no Recheck entry" + }, + { + "expect": "gone", + "name": "Open containing folder", + "label": "The context menu has no Open containing folder entry" + }, + { + "expect": "alive", + "label": "The application does not crash" + } + ], + "script_for": "a23ab5dd15e4" + }, + "t053b7c9c": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open XML", + "role": "menu item", + "label": "Choose File > Open XML" + }, + { + "do": "wait", + "name": "Open the report file", + "timeout": 10, + "label": "Wait for the Open XML dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:xml-results}/results.xml", + "label": "Enter the path of results.xml" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the XML file" + }, + { + "do": "wait", + "name": "Open the report file", + "gone": true, + "timeout": 10, + "label": "Wait for the dialog to close" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu again" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose File > Open Project File" + }, + { + "do": "wait", + "name": "Select Project File", + "timeout": 10, + "label": "Wait for the project file dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "do": "wait", + "name": "Select Project File", + "gone": true, + "timeout": 10, + "label": "Wait for the dialog to close" + }, + { + "do": "sleep", + "seconds": 5, + "label": "Wait until the analysis is done" + }, + { + "expect": "visible", + "name": "Reanalyze all files", + "role": "button", + "timeout": 10, + "label": "Reanalyze all files button is shown" + }, + { + "expect": "state", + "name": "Reanalyze all files", + "role": "button", + "state": "enabled", + "timeout": 10, + "label": "Reanalyze all files button is enabled" + }, + { + "do": "click", + "name": "Reanalyze all files", + "role": "button", + "label": "Click Reanalyze all files" + }, + { + "expect": "alive", + "label": "Application still running after the click" + } + ], + "script_for": "36a581ae5136" + } +} diff --git a/gui/manualtest/web.py b/gui/manualtest/web.py index 0edacb07dbd..a1d7226272d 100644 --- a/gui/manualtest/web.py +++ b/gui/manualtest/web.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Web interface for editing, generating and running the JSON tests. +"""Web interface for editing, generating and running the Gherkin tests. web.py [--opensource APP] [--premium APP] [--src DIR] [--dir tests] [--out results] [--port 8080] -Serves on 127.0.0.1 only. Reads and writes DIR/*.json and starts gen.py and run.py with the -application that is tested, one binary per edition. SRC is the git checkout that both are built +Serves on 127.0.0.1 only. Reads and writes DIR/*.feature, with the scripts in DIR/*.scripts.json, +and starts gen.py and run.py with the application that is tested, one binary per edition. SRC is the git checkout that both are built from, for the git sha in the reports. --app is the same as --opensource. Without any APP the tests can be edited but not generated or run. """ @@ -26,7 +26,7 @@ import testfile HERE = os.path.dirname(os.path.abspath(__file__)) -NAME_RE = re.compile(r'^[\w.-]+\.json$') +NAME_RE = re.compile(r'^[\w.-]+\.feature$') ID_RE = re.compile(r'^\w+$') @@ -138,7 +138,7 @@ def allowed(self): def path_of(self, query): name = parse_qs(query).get('name', [''])[0] if not NAME_RE.match(name): - self.reply(400, {'error': 'bad file name (letters, digits, _ - . and .json)'}) + self.reply(400, {'error': 'bad file name (letters, digits, _ - . and .feature)'}) return None return os.path.join(self.directory, name) @@ -184,25 +184,26 @@ def annotate(self, doc): """Add _stale (the script is outdated) and _errors (what is wrong with the test or its script) to every test, for the web page. Not saved, see stamp_scripts.""" tests = doc.get('tests') if isinstance(doc, dict) else None + background = doc.get('background') if isinstance(doc, dict) and isinstance(doc.get('background'), list) else [] for t in tests if isinstance(tests, list) else []: if not isinstance(t, dict): continue - t['_stale'] = testfile.is_stale(t, self.directory) + t['_stale'] = testfile.is_stale(t, self.directory, background) t['_errors'] = testfile.validate({'tests': [t]}, self.directory) def file_status(self): """{file: {invalid: n, stale: n}}: tests with errors and tests whose script must be generated again. - invalid is 1 for a file that is not valid JSON or not a test file.""" + invalid is 1 for a file that is not valid Gherkin.""" out = {} for name in os.listdir(self.directory): if not NAME_RE.match(name): continue try: - with open(os.path.join(self.directory, name), encoding='utf-8') as f: - doc = testfile.ensure_ids(json.load(f)) + doc = testfile.ensure_ids(testfile.load(os.path.join(self.directory, name))) self.annotate(doc) tests = [t for t in doc['tests'] if isinstance(t, dict)] - bad = sum(1 for t in tests if t['_errors']) + (len(tests) != len(doc['tests'])) + bad = sum(1 for t in tests if t['_errors']) + (len(tests) != len(doc['tests'])) + bool( + testfile.validate(dict(doc, tests=[]), self.directory)) out[name] = {'invalid': bad, 'stale': sum(1 for t in tests if t['_stale'])} except (ValueError, OSError, KeyError, TypeError, AttributeError): out[name] = {'invalid': 1, 'stale': 0} @@ -254,12 +255,12 @@ def do_GET(self): path = self.path_of(u.query) if path: try: - with open(path, encoding='utf-8') as f: - doc = testfile.ensure_ids(json.load(f)) + doc = testfile.ensure_ids(testfile.load(path)) except FileNotFoundError: return self.reply(404, {'error': 'no such file'}) except ValueError as e: - return self.reply(400, {'error': '%s is not valid JSON: %s' % (os.path.basename(path), e)}) + return self.reply(400, {'error': '%s is not valid Gherkin (fix it in a text editor): %s' % ( + os.path.basename(path), e)}) self.annotate(doc) self.reply(200, doc) else: @@ -423,6 +424,8 @@ def do_DELETE(self): if path: try: os.remove(path) + if os.path.exists(testfile.scripts_path(path)): + os.remove(testfile.scripts_path(path)) self.reply(200, {'deleted': os.path.basename(path)}) except FileNotFoundError: self.reply(404, {'error': 'no such file'}) diff --git a/gui/manualtest/web/index.html b/gui/manualtest/web/index.html index 6ef99566a27..08a25409748 100644 --- a/gui/manualtest/web/index.html +++ b/gui/manualtest/web/index.html @@ -44,8 +44,6 @@ ul.tlist li.off { opacity: .55; font-style: italic; } .row { display: flex; gap: .6rem; align-items: end; flex-wrap: wrap; } .row > .grow { flex: 1 1 14rem; } -.axis { display: flex; gap: .5rem; margin-bottom: .35rem; align-items: center; } -.axis input.an { width: 10rem; flex: none; } .chips { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .4rem; } .flag { font-size: .75rem; border-radius: 999px; padding: 0 .45rem; margin-left: .3rem; white-space: nowrap; } .flag.invalid { background: var(--bad); color: #fff; } @@ -165,10 +163,10 @@

        GUI tester

        } $('#newfile').onclick = () => { if (!confirmDiscard()) return; - let n = prompt('File name (e.g. projects.json):', 'new.json'); + let n = prompt('File name (e.g. projects.feature):', 'new.feature'); if (!n) return; - if (!n.endsWith('.json')) n += '.json'; - open(n, {title: '', tests: []}); setDirty(true); + if (!n.endsWith('.feature')) n += '.feature'; + open(n, {title: '', background: ['Given the application is started with no project loaded'], tests: []}); setDirty(true); }; $('#import').onclick = () => $('#importfile').click(); $('#importfile').onchange = async e => { @@ -176,7 +174,7 @@

        GUI tester

        if (!f || !confirmDiscard()) return; try { const d = await api('/api/import', {method: 'POST', body: JSON.stringify({markdown: await f.text()})}); - open(f.name.replace(/\.\w+$/, '') + '.json', d); setDirty(true); + open(f.name.replace(/\.\w+$/, '') + '.feature', d); setDirty(true); setStatus('Imported, check it and save', 'ok'); } catch (err) { setStatus(err.message, 'bad'); } }; @@ -390,21 +388,68 @@

        GUI tester

        $('#jobstop').onclick = () => api('/api/job/stop', {method: 'POST', body: '{}'}).catch(e => setStatus(e.message, 'bad')); window.onbeforeunload = e => { if (dirty || pdirty) e.preventDefault(); }; -// ---- model helpers. Axes are edited as a list (the order matters, names must be unique) -function axesOf(t) { return t._axes || (t._axes = Object.entries(t.matrix || {}).map(([n, v]) => ({n, v: v.join(', ')}))); } +// ---- model helpers. Steps are Gherkin lines, the Examples are edited as a Gherkin table +const STEP_RE = /^(Given|When|Then|And|But|\*)\s+\S/; +function toLines(text) { // lines of the steps; blank lines are dropped, except in doc strings + const out = []; let fence = null, indent = 0; // doc string lines lose the indentation of the opening """ + for (const raw of text.split('\n')) { + const s = raw.trim(); + if (fence) { + const lead = raw.length - raw.trimStart().length; + out.push(s === fence ? s : raw.trimEnd().slice(Math.min(lead, indent))); + if (s === fence) fence = null; + continue; + } + if (!s) continue; + out.push(s); + if (s.startsWith('"""') || s.startsWith('```')) { fence = s.slice(0, 3); indent = raw.length - raw.trimStart().length; } + } + return out; +} +function stepErrors(lines, need_then) { + const errs = []; let fence = null, first = true; + lines.forEach((l, k) => { + const s = l.trim(); + if (fence) { if (s === fence) fence = null; return; } + if (s.startsWith('"""') || s.startsWith('```')) { fence = s.slice(0, 3); return; } + if (s.startsWith('|')) return; + const m = STEP_RE.exec(s); + if (!m) errs.push('line ' + (k + 1) + ' must start with Given, When, Then, And, But or *'); + else if (first && (m[1] === 'And' || m[1] === 'But')) errs.push('the first step can not start with ' + m[1]); + first = false; + }); + if (fence) errs.push('a doc string is not closed with ' + fence); + if (need_then && !lines.some(l => /^Then\s/.test(l.trim()))) errs.push('needs a Then step, the expected result'); + return errs; +} +function fmtTable(rows) { + if (!rows || !rows.length) return ''; + const cols = Object.keys(rows[0]), w = cols.map(c => Math.max(c.length, ...rows.map(r => (r[c] || '').length))); + const line = cells => '| ' + cells.map((v, k) => v.padEnd(w[k])).join(' | ') + ' |'; + return [line(cols), ...rows.map(r => line(cols.map(c => r[c] || '')))].join('\n'); +} +function parseTable(text) { // {rows, err} + const lines = text.split('\n').map(s => s.trim()).filter(Boolean); + if (!lines.length) return {rows: []}; + const cells = l => l.replace(/^\|/, '').replace(/\|$/, '').split('|').map(c => c.trim()); + if (lines.some(l => !l.startsWith('|') || !l.endsWith('|') || l.length < 2)) return {rows: [], err: 'every line of the Examples must start and end with |'}; + const head = cells(lines[0]); + if (lines.length < 2) return {rows: [], err: 'the Examples need a header line and at least one row'}; + const rows = []; + for (const l of lines.slice(1)) { + const c = cells(l); + if (c.length !== head.length) return {rows: [], err: 'a row of the Examples has ' + c.length + ' cells, the header has ' + head.length}; + rows.push(Object.fromEntries(head.map((h, k) => [h, c[k]]))); + } + return {rows}; +} +function examplesText(t) { return t._exText !== undefined ? t._exText : (t._exText = fmtTable(t.examples)); } +function examplesOf(t) { return parseTable(examplesText(t)); } function fmtScript(script) { return script && script.length ? '[\n' + script.map(x => ' ' + JSON.stringify(x)).join(',\n') + '\n]' : ''; } let scriptDoc = ''; -function values(a) { return a.v.split(',').map(s => s.trim()).filter(Boolean); } -function combos(t) { - let out = [{}]; - for (const a of axesOf(t)) { - if (!a.n) continue; - out = out.flatMap(c => values(a).map(v => ({...c, [a.n]: v}))); - } - return out; -} +function combos(t) { const r = examplesOf(t).rows; return r.length ? r : [{}]; } function editionsOf(t) { return t.editions || config.editions; } function canRun(t, generate) { // a binary for an edition of the test; to run also the chosen edition const ed = $('#edition').value; @@ -419,29 +464,33 @@

        GUI tester

        else if (names.has(t.name)) errs.push(w + 'the name "' + t.name + '" is used twice'); names.add(t.name); if (t._scriptErr) errs.push(w + 'the script is not valid JSON: ' + t._scriptErr); - const an = new Set(); - for (const a of axesOf(t)) { - if (!/^\w+$/.test(a.n)) errs.push(w + 'axis name "' + a.n + '" must be letters, digits and _'); - else if (an.has(a.n)) errs.push(w + 'the axis "' + a.n + '" exists twice'); - an.add(a.n); - if (!values(a).length) errs.push(w + 'the axis "' + a.n + '" has no values'); - } + for (const e of stepErrors(t.steps || [], true)) errs.push(w + 'steps: ' + e); + const ex = examplesOf(t); + if (ex.err) errs.push(w + ex.err); + const cols = ex.rows.length ? Object.keys(ex.rows[0]) : []; + for (const c of cols) if (!/^\w+$/.test(c)) errs.push(w + 'the Examples column "' + c + '" must be letters, digits and _'); + if (new Set(cols).size !== cols.length) errs.push(w + 'an Examples column exists twice'); + if (ex.rows.some(r => Object.values(r).some(v => !v))) errs.push(w + 'every cell of the Examples needs a value'); + if (new Set(ex.rows.map(r => JSON.stringify(r))).size !== ex.rows.length) errs.push(w + 'a row of the Examples is there twice'); }); return errs; } function clean() { // what is saved: no editor-only fields, no empty optional fields - return {title: doc.title || '', tests: doc.tests.map(t => { + const head = {title: doc.title || ''}; + for (const k of ['background', 'description', 'tags']) if (doc[k] && doc[k].length) head[k] = doc[k]; + return {...head, tests: doc.tests.map(t => { const o = {id: t.id, name: t.name.trim()}; if (t.ticket) o.ticket = t.ticket; if (t.enabled === false) o.enabled = false; - o.steps = t.steps || []; o.expected = t.expected || ''; + o.steps = t.steps || []; + for (const k of ['description', 'tags']) if (t[k] && t[k].length) o[k] = t[k]; if (t.todo) o.todo = t.todo; if (t.script && t.script.length) o.script = t.script; if (t._approve) o._approve = true; if (t.projects && t.projects.length) o.projects = t.projects; if (t.editions && t.editions.length < config.editions.length) o.editions = t.editions; // default: all - const ax = axesOf(t).filter(a => a.n); - if (ax.length) o.matrix = Object.fromEntries(ax.map(a => [a.n, values(a)])); + const ex = examplesOf(t).rows; + if (ex.length) o.examples = ex; return o; })}; } @@ -453,6 +502,9 @@

        GUI tester

        + + +
          @@ -465,8 +517,11 @@

          GUI tester

          }; $('#runfile').onclick = () => startJob('run', undefined, 'all tests in ' + current, current); $('#title').oninput = e => { doc.title = e.target.value; setDirty(true); }; + const bgerr = () => $('#bgerr').textContent = stepErrors(doc.background || [], false).join('; '); + $('#background').oninput = e => { doc.background = toLines(e.target.value); setDirty(true); bgerr(); }; + bgerr(); $('#addtest').onclick = () => { - doc.tests.push({id: 'n' + Math.random().toString(36).slice(2, 10), name: 'New test', steps: [], expected: ''}); + doc.tests.push({id: 'n' + Math.random().toString(36).slice(2, 10), name: 'New test', steps: []}); sel = doc.tests.length - 1; setDirty(true); render(); $('#detail .name').select(); }; @@ -507,10 +562,9 @@

          GUI tester

          - - - - + + +
          @@ -527,16 +581,17 @@

          GUI tester

          How to write a script
          ${esc(scriptDoc)}
          - -
          - + +
          `; const on = (sel, ev, f) => d.querySelector(sel).addEventListener(ev, f); const edit = (sel, f) => on(sel, 'input', e => { f(e.target); setDirty(true); preview(); }); edit('.name', el => { t.name = el.value; renderList(); }); edit('.ticket', el => t.ticket = el.value); - edit('.steps', el => t.steps = el.value.split('\n').map(s => s.trim()).filter(Boolean)); - edit('.expected', el => t.expected = el.value); + const serr = () => d.querySelector('.steps-err').textContent = stepErrors(t.steps || [], true).join('; '); + edit('.steps', el => { t.steps = toLines(el.value); serr(); }); + edit('.examples', el => t._exText = el.value); + serr(); edit('.todo', el => t.todo = el.value); const pick = d.querySelector('.projpick'); const names = [...new Set([...projects.map(p => p.name), ...(t.projects || [])])]; @@ -590,35 +645,17 @@

          GUI tester

          if (!confirm('Delete "' + t.name + '"?')) return; doc.tests.splice(i, 1); sel = Math.min(i, doc.tests.length - 1); setDirty(true); render(); }); - on('.addaxis', 'click', () => { axesOf(t).push({n: '', v: ''}); setDirty(true); axes(); }); - function axes() { - const box = d.querySelector('.axes'); - box.innerHTML = ''; - axesOf(t).forEach((a, k) => { - const r = document.createElement('div'); - r.className = 'axis'; - r.innerHTML = ` - - `; - r.querySelector('.an').oninput = e => { a.n = e.target.value.trim(); setDirty(true); preview(); }; - r.querySelector('.av').oninput = e => { a.v = e.target.value; setDirty(true); preview(); }; - r.querySelector('.rm').onclick = () => { axesOf(t).splice(k, 1); setDirty(true); axes(); }; - box.appendChild(r); - }); - preview(); - } function preview() { - const p = d.querySelector('.preview'), cs = combos(t), ax = axesOf(t).filter(a => a.n); - if (!ax.length) { p.innerHTML = ''; return; } + const p = d.querySelector('.preview'), rows = examplesOf(t).rows, cs = combos(t); const label = c => Object.entries(c).map(([k, v]) => k + '=' + v).join(', '); - p.innerHTML = `
          ${cs.length} run${cs.length == 1 ? '' : 's'}
          ` + + p.innerHTML = !rows.length ? '' : `
          ${cs.length} run${cs.length == 1 ? '' : 's'}
          ` + `
          ${cs.slice(0, 40).map(c => `${esc(label(c))}`).join('')}` + (cs.length > 40 ? `… +${cs.length - 40}` : '') + '
          '; - const bad = problems().filter(e => e.startsWith('Test ' + (i + 1) + ':')); + const bad = problems().filter(e => e.startsWith('Test ' + (i + 1) + ':') && !e.includes(': steps: ')); if (bad.length) p.innerHTML += bad.map(e => `
          ${esc(e)}
          `).join(''); } - axes(); + preview(); return d; } function move(i, dir) { From 7ddf22ff44e133a16e6fde70857a2f776e36dbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 24 Oct 2026 10:26:35 +0200 Subject: [PATCH 3/6] gui manualtest: group boxes in the left column of the web editor Test files, Test projects and Results are shown in group boxes. The Import .md button is removed, testfile.py convert imports old files. Co-Authored-By: Claude Opus 5.5 --- gui/manualtest/docs/handbook.html | 4 ++-- gui/manualtest/web.py | 4 +--- gui/manualtest/web/index.html | 36 ++++++++++++------------------- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/gui/manualtest/docs/handbook.html b/gui/manualtest/docs/handbook.html index d81bd495115..8fb0dcad831 100644 --- a/gui/manualtest/docs/handbook.html +++ b/gui/manualtest/docs/handbook.html @@ -152,7 +152,7 @@

          Getting started

          The web interface

            -
          • Left column: the test files and the test projects. The ▶ button next to a file runs all tests in it.
          • +
          • Left column: three boxes: the test files, the test projects and the results (the reports of earlier runs). The ▶ button next to a file runs all tests in it.
          • Header: Save, Run all (every test in every file), the edition picker (opensource, premium or both) and a status message.
          • Main area: the open test file, the open test or test project. Each has an ✕ in the top right corner to close it.
          • Job box: while a script is generated or tests run, it shows the progress, the results as they come and a link to the report. Its ✕ hides it until the next job.
          • @@ -172,7 +172,7 @@

            Writing tests

            When I click Clear results Then the results are cleared: the result tree is empty

            A file is one Feature, its title is the title of the file. The Background steps belong to every scenario of the file. The fields that are not steps are tags above the scenario: @id:, @ticket:, @project: (one per project), @edition: (one per edition, none means all) and @disabled. A step can have a doc string (lines between """) or a table. The files can be edited in the web interface or in a text editor; comments (#) are lost when the web interface saves the file. The scripts are not in the feature file but next to it, in FILE.scripts.json, by test id.

            -

            Tests live in test files (tests/*.feature), one file per area of the GUI. Click a file in the left column to see its title and its tests, New file to create one, or Import .md to convert an older markdown test file (sections ## Test: NAME). Older JSON test files are converted with testfile.py convert IN.json OUT.feature. Click a test in the list to edit it, + New test to add one. Nothing is written to disk until you press Save; a dot after the file name means there are unsaved changes.

            +

            Tests live in test files (tests/*.feature), one file per area of the GUI. Click a file in the left column to see its title and its tests, New file to create one. Older markdown (sections ## Test: NAME) and JSON test files are converted with testfile.py convert IN.md|IN.json OUT.feature. Click a test in the list to edit it, + New test to add one. Nothing is written to disk until you press Save; a dot after the file name means there are unsaved changes.

            diff --git a/gui/manualtest/web.py b/gui/manualtest/web.py index a1d7226272d..3deab02fb49 100644 --- a/gui/manualtest/web.py +++ b/gui/manualtest/web.py @@ -343,9 +343,7 @@ def do_POST(self): route = urlparse(self.path).path try: data = self.body() - if route == '/api/import': - self.reply(200, testfile.from_markdown(data['markdown'])) - elif route == '/api/job': + if route == '/api/job': self.start_job(data) elif route == '/api/job/stop': if self.job and self.job.rc is None: diff --git a/gui/manualtest/web/index.html b/gui/manualtest/web/index.html index 08a25409748..3012a4ef925 100644 --- a/gui/manualtest/web/index.html +++ b/gui/manualtest/web/index.html @@ -31,6 +31,9 @@ textarea { resize: vertical; min-height: 4.5rem; font-family: ui-monospace, monospace; font-size: .9rem; } label { display: block; font-size: .8rem; color: var(--muted); margin: .6rem 0 .15rem; } ul.files { list-style: none; margin: 0; padding: 0; } +fieldset.group { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; margin: 0 0 .9rem; padding: .3rem .4rem .5rem; min-width: 0; } +fieldset.group legend { padding: 0 .35rem; font-size: .8rem; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; } +fieldset.group p { margin: .4rem .2rem 0; } ul.files li { padding: .35rem .6rem; border-radius: 6px; cursor: pointer; overflow-wrap: anywhere; } ul.files li.sel { background: var(--chip); font-weight: 600; } .card { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: .75rem 1rem; margin-bottom: .9rem; } @@ -87,18 +90,17 @@

            GUI tester

            Select a file on the left or create a new one.

            - + @@ -158,6 +160,91 @@

            The web interface

          • Job box: while a script is generated or tests run, it shows the progress, the results as they come and a link to the report. Its ✕ hides it until the next job.
          • +

            Gherkin

            +

            The manual tests are written in Gherkin, the language that Cucumber, behave and other behaviour-driven development tools use. Gherkin describes behaviour in plain words with a small set of keywords, so a test reads the same for a developer, a tester and Claude. Each test file in tests/ is a .feature file with one feature, and every scenario in it is one test.

            +
            Feature: Preferences
            +
            +  Background:
            +    Given the application is started with no project loaded
            +
            +  @id:t28ce8afe @project:embedded @edition:opensource
            +  Scenario Outline: Misra rule text file
            +    Given Edit > Preferences is open on the Addons tab
            +    When I select the rule text file {project:embedded}/<rulefile>
            +    And I close the dialog with OK
            +    And I open the project file {project:embedded}/embedded.cppcheck
            +    And I wait until the analysis is done
            +    Then the Misra error for the unused parameter has the rule text from the file
            +    And there are no internalError results
            +
            +    Examples:
            +      | rulefile              |
            +      | rules.txt             |
            +      | rules with spaces.txt |
            + +

            Keywords

            +
            FieldWhat to write
            run.pyReplays the scripts without AI and writes the report and results.json
            runner.pyExecutes one script step by step, used by both gen.py and run.py
            guitest.pyCommand line tool that starts the session and clicks, types and reads the GUI
            testfile.pyThe test file format, test matrices, test projects and outdated-script detection
            testfile.pyThe Gherkin parser and writer, Examples, test projects and outdated-script detection
            report.pyWrites the HTML report
            tests/The test files (*.feature), their scripts (*.scripts.json) and test projects (tests/projects/NAME)
            results/One directory per run with the report, logs and screenshots
            + + + + + + + + + + + + + + + + +
            KeywordMeaning here
            Feature:The first line of the file. Its text is the title of the test file, shown in the editor and the report. Lines below it, before the first scenario, are a free description
            Background:Steps that come before every scenario of the file, typically Given the application is started with no project loaded. At most one, before the scenarios
            Scenario:One test. The text after the colon is the name of the test, unique within the file. Example: means the same
            Scenario Outline:A test that is run once for every row of its Examples table. Scenario Template: means the same
            Examples:The table of a scenario outline. The first row is the header with the column names; <column> in a step is replaced by the value of the row. See Examples (test matrix)
            GivenThe starting point: the state that the application is in before the test does anything
            WhenAn action of the user: a click, a menu entry, typing, opening a file
            ThenThe expected result: something that can be seen in the GUI or in a file. Every scenario needs at least one Then
            And, But, *Continue the step above with the same meaning, e.g. a second Then. A scenario can not start with And or But
            """A doc string: lines of text that belong to the step above, e.g. code to type into the scratch pad. It ends with another """
            | a | b |A data table that belongs to the step above
            @tagTags on the line above a scenario, see below
            # textA comment. Comments are lost when the web interface saves the file
            TODO:Not standard Gherkin: a description line under the scenario name that says why the test can't be automated yet
            +

            A scenario can switch between When and Then more than once, when a result has to be checked in the middle of the test:

            +
              Scenario: Filter the results
            +    Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu
            +    And the analysis is done
            +    When I type "arrayIndex" in the Filter field
            +    Then only the arrayIndexOutOfBounds error in b.c is shown
            +    When I clear the Filter field
            +    Then all errors are shown again
            + +

            Tags

            +

            Everything about a test that is not a step is a tag on the line above the scenario. Tags can't contain spaces.

            +
            + + + + + + + +
            TagMeaning
            @id:t1a2b3c4dIdentifies the test, also when it is renamed. The script is stored by this id. Added automatically when it is missing; don't change it
            @ticket:14893The ticket of the test, shown as #14893
            @project:NAMEA test project that the test uses, one tag per project
            @edition:opensourceAn edition that the test is for, one tag per edition. Without the tag the test is for all editions
            @disabledThe test is not run
            +

            Other tags are kept but have no meaning for gui-test.

            + +

            Writing good steps

            +

            Claude reads the steps as they are when it makes the script, and the Then steps become the checks. A good scenario is one that a new tester could follow without asking:

            +
              +
            • Use the words of the GUI: the menu entries, button texts and dialog titles as they are shown, e.g. When I open File > Edit Project File.
            • +
            • Write Given steps as a state (Given the analysis is done), When steps as an action (When I click Recheck) and Then steps as something that can be seen (Then the nullPointer error in a.c is gone).
            • +
            • One thing per Then step. Then there are no results for a.c and And there are no results for b.c become two checks, and a failure points at the right one.
            • +
            • Be concrete about results: name the file, the error id and the line instead of "the results are correct".
            • +
            • Say how to wait: And I wait until the analysis is done.
            • +
            • Refer to files with {project:NAME}/path, and to the scratch directory with {work}.
            • +
            • Keep the steps that all tests of a file share in the Background.
            • +
            +

            Changing the steps, the Background or the Examples makes the script of the test outdated, because the script was made for the old words.

            + +

            Differences from Cucumber

            +
              +
            • There are no step definitions. In Cucumber every step is matched by a pattern to code that someone writes. Here Claude reads the scenario once and writes a script for it, and the script is replayed. Steps can therefore be free text; they don't have to repeat fixed phrases.
            • +
            • The script of a scenario is stored next to the feature file in FILE.scripts.json, by test id.
            • +
            • Rule:, tags on Examples, and more than one Feature per file are not supported.
            • +
            • Placeholders in braces are gui-test variables, not Gherkin: {project:NAME}, {work} and {edition}.
            • +
            • TODO: description lines are a gui-test convention.
            • +
            +

            Writing tests

            A test is a Gherkin scenario, written as if for a person: Given steps say where the test starts, When steps what is done and Then steps the result to expect. Claude reads exactly these words when it makes the script, so be concrete.

            Feature: Results list
            @@ -171,7 +258,7 @@ 

            Writing tests

            And the analysis is done When I click Clear results Then the results are cleared: the result tree is empty
            -

            A file is one Feature, its title is the title of the file. The Background steps belong to every scenario of the file. The fields that are not steps are tags above the scenario: @id:, @ticket:, @project: (one per project), @edition: (one per edition, none means all) and @disabled. A step can have a doc string (lines between """) or a table. The files can be edited in the web interface or in a text editor; comments (#) are lost when the web interface saves the file. The scripts are not in the feature file but next to it, in FILE.scripts.json, by test id.

            +

            The files can be edited in the web interface or in a text editor. The Gherkin chapter describes the format; the web interface shows the tags of a scenario (ticket, editions, test projects, enabled) as separate fields.

            Tests live in test files (tests/*.feature), one file per area of the GUI. Click a file in the left column to see its title and its tests, New file to create one. Older markdown (sections ## Test: NAME) and JSON test files are converted with testfile.py convert IN.md|IN.json OUT.feature. Click a test in the list to edit it, + New test to add one. Nothing is written to disk until you press Save; a dot after the file name means there are unsaved changes.

            From fd912140b69e0415d5ac091e82333217770ec1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 24 Oct 2026 16:41:32 +0200 Subject: [PATCH 6/6] handbook --- gui/manualtest/docs/handbook.html | 73 +++++++++++++++---------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/gui/manualtest/docs/handbook.html b/gui/manualtest/docs/handbook.html index 766a0345a81..1da9f571224 100644 --- a/gui/manualtest/docs/handbook.html +++ b/gui/manualtest/docs/handbook.html @@ -76,7 +76,13 @@

            gui-test Handbook

            Updated 2026-10-24

            Overview

            -

            A test is written in Gherkin: a scenario with Given, When and Then steps in plain words. gen.py starts the application, lets Claude do the test by hand through guitest.py, and saves what it did as a script of do and expect steps. run.py replays the scripts, one fresh application per test, and writes an HTML report. web.py is the web interface around all of it.

            +

            Working with gui-test has three steps:

            +
              +
            1. Write the test in plain words: what the starting point is, what you do and what you expect to see.
            2. +
            3. Generate the script, once. Claude does the test in the application and writes down what it did as a script.
            4. +
            5. Run the tests as often as you like. The scripts are replayed without AI, and you get a report with the results and screenshots.
            6. +
            +

            All of this is done in the web interface. The application runs on its own virtual screen, so your desktop is not touched while tests run.

            @@ -114,20 +120,7 @@

            Overview

            From a test in words to a report: gen.py once, run.py every run.
            -

            The application runs in its own Xvfb display with its own D-Bus and accessibility bus, so your desktop is never touched. Elements are found through AT-SPI by their accessible name and role, not by screen positions.

            -
            FieldWhat to write
            - - - - - - - - - - - -
            PartWhat it does
            web.pyWeb interface: edit test files and test projects, generate scripts, run tests, open reports
            gen.pyClaude drives the GUI once per test and writes its script; the script is saved only if a replay passes
            run.pyReplays the scripts without AI and writes the report and results.json
            runner.pyExecutes one script step by step, used by both gen.py and run.py
            guitest.pyCommand line tool that starts the session and clicks, types and reads the GUI
            testfile.pyThe Gherkin parser and writer, Examples, test projects and outdated-script detection
            report.pyWrites the HTML report
            tests/The test files (*.feature), their scripts (*.scripts.json) and test projects (tests/projects/NAME)
            results/One directory per run with the report, logs and screenshots
            +

            The tests are kept in tests/, and every run puts its report, logs and screenshots in a new directory under results/.

            Getting started

            Start web.py with the cppcheck-gui binaries you want to test, then open http://127.0.0.1:8080/ in a browser.

            @@ -138,26 +131,21 @@

            Getting started

          • --src is the git checkout the binaries are built from. It is only used for the git SHA, branch and commit in the report; the default is the directory of the binary.
          • --dir (default tests), --out (default results) and --port (default 8080) change where test files and results live and which port is used. The server only listens on 127.0.0.1.
          -

          The tools the session needs must be installed: Xvfb, xdotool, dbus-daemon, the AT-SPI bus launcher and Python with the GObject bindings and Pillow. tesseract is only needed for ocr checks, and the claude command line tool only for generating scripts. On Ubuntu (checked on 26.04) they come from these packages:

          +

          Some packages must be installed first. On Ubuntu (checked on 26.04):

          sudo apt install xvfb xdotool dbus at-spi2-core python3-gi gir1.2-atspi-2.0 python3-pil tesseract-ocr tesseract-ocr-eng
          -
          - - - - - - - - - -
          PackageNeeded for
          xvfbThe virtual display the application runs in
          xdotoolKeyboard input and mouse clicks
          dbusThe session's own D-Bus (dbus-daemon)
          at-spi2-coreThe accessibility bus (/usr/libexec/at-spi-bus-launcher)
          python3-gi, gir1.2-atspi-2.0Python access to AT-SPI, to find and read elements
          python3-pilScreenshots, and cropping them for the report
          tesseract-ocr, tesseract-ocr-engOnly for ocr checks
          +

          The tesseract packages are only needed for ocr checks. To generate scripts the claude command line tool must be installed too.

          The web interface

            -
          • Left column: three boxes: the test files, the test projects and the results (the reports of earlier runs). The ▶ button next to a file runs all tests in it.
          • -
          • Header: Save, Run all (every test in every file), the edition picker (opensource, premium or both) and a status message.
          • -
          • Main area: the open test file, the open test or test project. Each has an ✕ in the top right corner to close it.
          • -
          • Job box: while a script is generated or tests run, it shows the progress, the results as they come and a link to the report. Its ✕ hides it until the next job.
          • +
          • Header: Save, Run all (every test in every file), the Edition picker, a status message and a link to this handbook. The Edition picker only shows the editions that web.py got a binary for, and "both" when it got two. Without a binary there is no picker, and tests can be edited but not generated or run.
          • +
          • Left column: three boxes. +
              +
            • Test files, with New file. A file is flagged invalid when a test in it has errors, and regenerate N when N tests have an outdated script.
            • +
            • Test projects, with New test project.
            • +
            • Results: Reports lists the earlier runs with their date, edition, version, test files and result. Click a date to open the report.
            • +
          • +
          • Main area: the open test file with its tests, the open test, or the open test project. Each has an ✕ in the top right corner to close it.
          • +
          • Job box: while a script is generated or tests run, it shows the progress, the results as they come in and a link to the report. Stop ends the job. Its ✕ hides the box until the next job.

          Gherkin

          @@ -259,17 +247,26 @@

          Writing tests

          When I click Clear results Then the results are cleared: the result tree is empty

          The files can be edited in the web interface or in a text editor. The Gherkin chapter describes the format; the web interface shows the tags of a scenario (ticket, editions, test projects, enabled) as separate fields.

          -

          Tests live in test files (tests/*.feature), one file per area of the GUI. Click a file in the left column to see its title and its tests, New file to create one. Older markdown (sections ## Test: NAME) and JSON test files are converted with testfile.py convert IN.md|IN.json OUT.feature. Click a test in the list to edit it, + New test to add one. Nothing is written to disk until you press Save; a dot after the file name means there are unsaved changes.

          +

          Tests live in test files (tests/*.feature), one file per area of the GUI. Click a file in the left column to open it, or New file to create one. An open file shows:

          +
            +
          • Title: the Feature: line, shown in the report.
          • +
          • Background: the steps that are done before every test of the file, e.g. Given the application is started with no project loaded.
          • +
          • Tests: the list of tests. Click one to edit it, + New test to add one. A test is flagged invalid when it has errors and regenerate when its script is outdated.
          • +
          • Run tests: runs all tests in the file.
          • +
          +

          Nothing is written to disk until you press Save; a dot after the file name means there are unsaved changes. Errors, such as a test without a Then step, are shown under the field and must be fixed before you can save.

          +

          The test editor has these fields. The ↑ and ↓ buttons move the test in the list, and Delete removes it.

          - + - + +
          FieldWhat to write
          NameUnique within the file. Shown in reports and the history, so a rename starts a new history line
          TicketOptional, e.g. a Trac ticket number
          enabledUntick to leave the test out of every run without deleting it
          EditionsThe editions the test runs in; default both. The script is the same for all, {edition} is replaced by the edition's name
          EditionsTick the editions the test runs in; by default all. The script is the same for all editions, {edition} is replaced by the edition's name
          Scenario stepsOne Gherkin step per line, e.g. "When I open File > New Project File". Every Then step becomes at least one check in the script. Use <column> for the values of the Examples
          TODOWhy a test can't be automated yet, e.g. "cannot be automated". Leave empty otherwise. In the file it is a line TODO: ... below the scenario name
          Test projectsThe projects whose files the test uses, see below
          Test projectsTick the projects whose files the test uses, see below
          ScriptThe script of the test, with the buttons Generate script, Run test and, when it is outdated, Approve the script. See Scripts
          ExamplesA Gherkin table; every row is a separate run and the test becomes a Scenario Outline
          @@ -340,7 +337,7 @@

          From the web interface

          Pick the edition in the header first: opensource, premium or both. Then:

          • Run test in an open test runs that test, all the rows of its Examples.
          • -
          • ▶ next to a test file runs every test in that file.
          • +
          • Run tests in an open test file runs every test in that file.
          • Run all runs every test in every file.

          With "both", the run is done for one edition and then the other, and each edition gets its own report. The job box shows the test and step that is running and the results as they come. Stop ends the run at once: the test that was running is SKIP "aborted by user", and the report is written with the tests that are done. Only one job runs at a time.

          @@ -363,7 +360,7 @@

          From the command line

          What happens in a run

          1. The tests to run are selected: enabled, for this edition, not filtered out. Tests without a script, or with an outdated one, are SKIP.
          2. -
          3. For each test, a new Xvfb session starts, the test projects are copied, and the application is started.
          4. +
          5. For each test, the test projects are copied and the application is started on a new virtual screen with an empty HOME.
          6. The script is replayed step by step. After every step that acts on the GUI (click, type, key, settext, restart...) a screenshot is saved.
          7. At the end, or at the first failing step, the step log, a final screenshot and the application log are saved, and the log is searched for a crash.
          8. After the last test, results.json and report.html are written in the run directory.
          9. @@ -392,7 +389,7 @@

            The step strip

            Every test has a strip with a cell per script step: green passed, red failed, grey not run. A failure at step 2 of 14 usually means the test or the application broke early. A failure at the last step usually means one check is wrong, or the bug the check describes. Under the strip it says e.g. "failed at step 9 of 14", "not run" or "did not start".

            Files in a run directory

            -

            A run directory is results/<date>-<time>-<edition>-<sha>/, e.g. 20261022-120317-opensource-b8b27a95/. The test name becomes the file name, with everything but letters, digits and _ turned into _.

            +

            A run directory is results/<date>-<time>-<edition>-<sha>/, e.g. 20261022-120317-opensource-b8b27a95/. The files of a test are named after the test (NAME below), with spaces and other special characters replaced by _. They are also linked from the report.

            @@ -403,7 +400,7 @@

            Files in a run directory

            FileContent
            NAME.stepNN.pngThe screen after step NN, for steps that act on the GUI
            NAME.app.logWhat the application printed
            -

            The history only uses runs of the same edition in the same --out directory that have a results.json. Runs made before 22 Oct 2026 don't have one and are left out. Tests are matched by name.

            +

            The history compares runs of the same edition in the same results directory. Tests are matched by name.

            Troubleshooting

            Start with the reason in the report and the final screenshot: together they usually say whether the application, the script or the test is wrong.