The GUI can be started from a terminal or a shortcut with options:
+
+
cppcheck-gui [OPTIONS] [files or paths]
+
+
+
Option
Description
+
-p <file>
Open the project file and analyze
+ it. If the project has a build dir with earlier results, these are shown
+ instead; click Reanalyze all files to analyze again.
+
-l <file>
Open a results file in XML
+ format.
+
-d <directory>
Used with -l:
+ the folder that was analyzed to create the results file. Relative paths in
+ the results file are relative to this folder. It is needed to show the
+ source code of the warnings.
+
-h, --help
Show a short description
+ of the options.
+
-v, --version
Show the version of
+ the GUI.
+
--data-dir=<directory>
Meant for
+ installation scripts. Stores the folder where Cppcheck's data files
+ (translations, library configurations, platforms, addons) are installed.
+ The GUI is not started when this option is used.
+
files or paths
Analyze these files and folders without
+ a project. If one of the arguments is a project file
+ (*.cppcheck), it is opened as with -p. If one of
+ them is an XML file, it is opened as a results file.
+
+
+
Examples
+
+
Open a project:
+
cppcheck-gui -p myproject.cppcheck
+
or simply:
+
cppcheck-gui myproject.cppcheck
+
+
Look at the results of a CI job. The job ran Cppcheck in the folder
+/home/me/src/myproject and saved the results:
When the GUI is started without options, it opens the project that was open
+when it was closed last time, and shows the results of the last analysis if
+the project has a build dir.
+
+
+
diff --git a/gui/help/images/add-application.png b/gui/help/images/add-application.png
new file mode 100644
index 00000000000..68e4b422ace
Binary files /dev/null and b/gui/help/images/add-application.png differ
diff --git a/gui/help/images/context-menu-tag.png b/gui/help/images/context-menu-tag.png
new file mode 100644
index 00000000000..69c510f3d48
Binary files /dev/null and b/gui/help/images/context-menu-tag.png differ
diff --git a/gui/help/images/context-menu.png b/gui/help/images/context-menu.png
new file mode 100644
index 00000000000..6afa8f80542
Binary files /dev/null and b/gui/help/images/context-menu.png differ
diff --git a/gui/help/images/index-mainwindow.png b/gui/help/images/index-mainwindow.png
deleted file mode 100644
index aa67a5e836d..00000000000
Binary files a/gui/help/images/index-mainwindow.png and /dev/null differ
diff --git a/gui/help/images/library-editor.png b/gui/help/images/library-editor.png
new file mode 100644
index 00000000000..5bdf7db02ba
Binary files /dev/null and b/gui/help/images/library-editor.png differ
diff --git a/gui/help/images/main-window.png b/gui/help/images/main-window.png
new file mode 100644
index 00000000000..e678199f2e0
Binary files /dev/null and b/gui/help/images/main-window.png differ
diff --git a/gui/help/images/menu-analyze.png b/gui/help/images/menu-analyze.png
new file mode 100644
index 00000000000..6aa110f8dd4
Binary files /dev/null and b/gui/help/images/menu-analyze.png differ
diff --git a/gui/help/images/menu-edit.png b/gui/help/images/menu-edit.png
new file mode 100644
index 00000000000..18b587ceb44
Binary files /dev/null and b/gui/help/images/menu-edit.png differ
diff --git a/gui/help/images/menu-file.png b/gui/help/images/menu-file.png
new file mode 100644
index 00000000000..4b7a3fcfd9f
Binary files /dev/null and b/gui/help/images/menu-file.png differ
diff --git a/gui/help/images/menu-help.png b/gui/help/images/menu-help.png
new file mode 100644
index 00000000000..a76d218063b
Binary files /dev/null and b/gui/help/images/menu-help.png differ
diff --git a/gui/help/images/menu-view-report.png b/gui/help/images/menu-view-report.png
new file mode 100644
index 00000000000..65050ce279c
Binary files /dev/null and b/gui/help/images/menu-view-report.png differ
diff --git a/gui/help/images/menu-view.png b/gui/help/images/menu-view.png
new file mode 100644
index 00000000000..40d66eaf972
Binary files /dev/null and b/gui/help/images/menu-view.png differ
diff --git a/gui/help/images/new-suppression.png b/gui/help/images/new-suppression.png
new file mode 100644
index 00000000000..13c615172e4
Binary files /dev/null and b/gui/help/images/new-suppression.png differ
diff --git a/gui/help/images/preferences-addons.png b/gui/help/images/preferences-addons.png
new file mode 100644
index 00000000000..70eb0642256
Binary files /dev/null and b/gui/help/images/preferences-addons.png differ
diff --git a/gui/help/images/preferences-applications.png b/gui/help/images/preferences-applications.png
new file mode 100644
index 00000000000..41a94dcda32
Binary files /dev/null and b/gui/help/images/preferences-applications.png differ
diff --git a/gui/help/images/preferences-clang.png b/gui/help/images/preferences-clang.png
new file mode 100644
index 00000000000..bb00ea2d31c
Binary files /dev/null and b/gui/help/images/preferences-clang.png differ
diff --git a/gui/help/images/preferences-code-editor.png b/gui/help/images/preferences-code-editor.png
new file mode 100644
index 00000000000..5edb3658d6d
Binary files /dev/null and b/gui/help/images/preferences-code-editor.png differ
diff --git a/gui/help/images/preferences-general.png b/gui/help/images/preferences-general.png
new file mode 100644
index 00000000000..98f56728201
Binary files /dev/null and b/gui/help/images/preferences-general.png differ
diff --git a/gui/help/images/preferences-language.png b/gui/help/images/preferences-language.png
new file mode 100644
index 00000000000..32aa867fcb2
Binary files /dev/null and b/gui/help/images/preferences-language.png differ
diff --git a/gui/help/images/preferences-reports.png b/gui/help/images/preferences-reports.png
new file mode 100644
index 00000000000..d18a150ac5a
Binary files /dev/null and b/gui/help/images/preferences-reports.png differ
diff --git a/gui/help/images/projectfile-addons.png b/gui/help/images/projectfile-addons.png
new file mode 100644
index 00000000000..9aa09829fbb
Binary files /dev/null and b/gui/help/images/projectfile-addons.png differ
diff --git a/gui/help/images/projectfile-analysis.png b/gui/help/images/projectfile-analysis.png
new file mode 100644
index 00000000000..1d846829455
Binary files /dev/null and b/gui/help/images/projectfile-analysis.png differ
diff --git a/gui/help/images/projectfile-import.png b/gui/help/images/projectfile-import.png
new file mode 100644
index 00000000000..ae487d9bf3c
Binary files /dev/null and b/gui/help/images/projectfile-import.png differ
diff --git a/gui/help/images/projectfile-paths-and-defines.png b/gui/help/images/projectfile-paths-and-defines.png
new file mode 100644
index 00000000000..a6ec3a4d2c8
Binary files /dev/null and b/gui/help/images/projectfile-paths-and-defines.png differ
diff --git a/gui/help/images/projectfile-types-and-functions.png b/gui/help/images/projectfile-types-and-functions.png
new file mode 100644
index 00000000000..4b2fb57f435
Binary files /dev/null and b/gui/help/images/projectfile-types-and-functions.png differ
diff --git a/gui/help/images/projectfile-warning-options.png b/gui/help/images/projectfile-warning-options.png
new file mode 100644
index 00000000000..779925309af
Binary files /dev/null and b/gui/help/images/projectfile-warning-options.png differ
diff --git a/gui/help/images/quick-filter.png b/gui/help/images/quick-filter.png
new file mode 100644
index 00000000000..cefbe833e42
Binary files /dev/null and b/gui/help/images/quick-filter.png differ
diff --git a/gui/help/images/report-misra.png b/gui/help/images/report-misra.png
new file mode 100644
index 00000000000..30071346a44
Binary files /dev/null and b/gui/help/images/report-misra.png differ
diff --git a/gui/help/images/scratchpad.png b/gui/help/images/scratchpad.png
new file mode 100644
index 00000000000..ca1dd74ad3d
Binary files /dev/null and b/gui/help/images/scratchpad.png differ
diff --git a/gui/help/images/statistics-previous-scan.png b/gui/help/images/statistics-previous-scan.png
new file mode 100644
index 00000000000..a2ec1c45157
Binary files /dev/null and b/gui/help/images/statistics-previous-scan.png differ
diff --git a/gui/help/images/statistics-project.png b/gui/help/images/statistics-project.png
new file mode 100644
index 00000000000..5e909732bf4
Binary files /dev/null and b/gui/help/images/statistics-project.png differ
diff --git a/gui/help/images/statistics-statistics.png b/gui/help/images/statistics-statistics.png
new file mode 100644
index 00000000000..1bbaddbe18f
Binary files /dev/null and b/gui/help/images/statistics-statistics.png differ
diff --git a/gui/help/images/toolbar.png b/gui/help/images/toolbar.png
new file mode 100644
index 00000000000..41664a4edb2
Binary files /dev/null and b/gui/help/images/toolbar.png differ
diff --git a/gui/help/images/walkthrough-analysis.png b/gui/help/images/walkthrough-analysis.png
deleted file mode 100644
index 94efb2890d8..00000000000
Binary files a/gui/help/images/walkthrough-analysis.png and /dev/null differ
diff --git a/gui/help/images/walkthrough-import-project.png b/gui/help/images/walkthrough-import-project.png
deleted file mode 100644
index 44b18e7f6b2..00000000000
Binary files a/gui/help/images/walkthrough-import-project.png and /dev/null differ
diff --git a/gui/help/images/walkthrough-library.png b/gui/help/images/walkthrough-library.png
deleted file mode 100644
index fbb5ae14d09..00000000000
Binary files a/gui/help/images/walkthrough-library.png and /dev/null differ
diff --git a/gui/help/images/walkthrough-new-project.png b/gui/help/images/walkthrough-new-project.png
deleted file mode 100644
index 55bf7ce2f4f..00000000000
Binary files a/gui/help/images/walkthrough-new-project.png and /dev/null differ
diff --git a/gui/help/images/walkthrough-toolbar-severities.png b/gui/help/images/walkthrough-toolbar-severities.png
deleted file mode 100644
index f06e3aa41cf..00000000000
Binary files a/gui/help/images/walkthrough-toolbar-severities.png and /dev/null differ
diff --git a/gui/help/images/walkthrough-warning-menu.png b/gui/help/images/walkthrough-warning-menu.png
deleted file mode 100644
index 0154b5ccbc9..00000000000
Binary files a/gui/help/images/walkthrough-warning-menu.png and /dev/null differ
diff --git a/gui/help/index.html b/gui/help/index.html
index 4d2cd43f824..6272ec01151 100644
--- a/gui/help/index.html
+++ b/gui/help/index.html
@@ -1,21 +1,103 @@
Cppcheck GUI
+
Cppcheck GUI
-
With the Cppcheck GUI you can analyze your code.
+
Cppcheck is a static analysis tool for C and C++ code. It looks for bugs
+such as undefined behavior, memory leaks, buffer overruns, null pointer
+dereferences and uninitialized variables, and it points out code that is
+suspicious, slow or hard to maintain. Cppcheck tries hard to avoid false
+positives: when it reports something there is usually a reason to look at
+it.
+
+
The Cppcheck GUI is a graphical front end for Cppcheck. With the GUI you
+can:
+
+
create and edit project files that describe what code to analyze and how
+ to analyze it,
+
import Visual Studio solutions and projects, compile databases
+ (compile_commands.json) and Borland C++ Builder 6 projects,
+
run the analysis in several threads and watch the results come in,
+
browse the warnings, read the source code next to them and follow the
+ path that leads to each bug,
+
filter, hide, suppress and tag warnings,
+
open the code in your favorite editor,
+
save results as XML, text or CSV and print them,
+
try out small pieces of code in the scratchpad,
+
write configuration files for your own libraries.
+
+
+
Contents
-
If you want to get started, look at the walk through.
With a project file (recommended). A project file
+ (*.cppcheck) stores where your code is, the include paths,
+ the preprocessor defines, the libraries you use, suppressions, tags and
+ much more. The analysis is more accurate when Cppcheck knows these
+ things, and you only configure them once. See
+ Getting started and
+ Project files.
+
Without a project file. Use Analyze > Files... or
+ Analyze > Directory... to analyze some code quickly. See
+ Analyzing without a project.
+
-
Example
+
More information
-
+
The Cppcheck manual describes the analysis itself in more detail, including
+the command line tool, the configuration file format and addons. You can find
+it at https://cppcheck.sourceforge.io/manual.pdf.
+
+
The Cppcheck home page is
+https://cppcheck.sourceforge.io/.
+There you can download new versions, read about the checks and report
+problems.
When you have run the analysis it is time to look at the results.
+
When the analysis is done (or while it is still running) it is time to look
+at the results. This page explains how to read a warning, how to decide if it
+is a real bug, and what you can do with it.
-
If you click on a warning then the corresponding code will be shown in the
-"Warning details" at the bottom.
+
Reading a warning
-
You can right click warnings to get options. The difference of
-"hiding" a warning and "suppressing" a warning is that
-the suppression is permanent and hiding the warning is only temporary. When
-suppressing warning(s), that is saved in the project file.
+
+
Click a warning in the results list. The lower part of the window then
+shows:
+
+
the id of the warning, for example arrayIndexOutOfBounds.
+ The id identifies the kind of problem. Search for it in the Cppcheck
+ documentation or on the internet to learn more about it,
+
the CWE number, if there is one. CWE (Common Weakness Enumeration)
+ is a standard list of software weaknesses. Look it up at
+ cwe.mitre.org for a general
+ description of the problem,
+
the full message. It can be longer than the summary in the list,
+
the source code with the line of the warning highlighted.
+
+
+
In the example above Cppcheck says that buf[16] is accessed at
+index 16. The array has 16 elements, so the valid indexes are 0 to 15. The loop
+condition i <= BUFFER_SIZE should be
+i < BUFFER_SIZE.
+
+
Notes: the path to the bug
+
+
Many warnings have notes. Click the arrow in front of the warning to see
+them. Notes show the steps that lead to the problem, in order, for
+example:
+
Assuming that condition 'i<=16' is not redundant (line 9)
+Array index out of bounds (line 10)
+
+
Click a note to show its line in the code view. For a bug that depends on
+values, the notes usually tell you where the value comes from, which
+conditions Cppcheck assumes, and where the bad value is used. Follow the notes
+to understand the warning.
+
+
A note like Assuming that condition 'x' is not redundant means that
+Cppcheck uses the condition in your code as a hint about possible values. For
+example, if the code checks if (p == NULL) after it has already
+used p, then either the check is not needed or the code can use a
+null pointer. Cppcheck warns because one of them must be wrong.
+
+
Is it a real bug?
+
+
Cppcheck tries to avoid false warnings, but no static analysis tool is
+perfect. To decide if a warning is right:
+
+
Read the message and the notes, and look at the code they point to.
+
Think about the values Cppcheck mentions. Can they really happen? Look at
+ the callers of the function if needed.
+
Check if Cppcheck may be missing information. Warnings in code that uses
+ macros or types that Cppcheck could not see, because an include path is
+ missing, can be wrong. Look for missingInclude information
+ messages and fix the include paths in the project.
+
Check if a library configuration is missing. If your code calls a
+ function that never returns (like a fatal error handler) and Cppcheck
+ does not know that, it may warn about code after the call. Select the
+ library in the project or write a configuration for your own functions.
+ See Libraries.
+
+
+
Even if a warning is not a bug today, it can point to code that is hard to
+understand or easy to break. Many developers prefer to change the code so that
+the warning goes away, instead of suppressing it.
+
+
If you are sure Cppcheck is wrong, suppress the warning (see
+Hiding and suppressing warnings). It is also
+very helpful if you report the false warning, with a small code example, at
+https://trac.cppcheck.net/ or in the
+Cppcheck forum.
+
+
Inconclusive warnings
+
+
Sometimes Cppcheck finds something suspicious but can not be sure that it
+is a bug. These inconclusive warnings are not shown by default. Enable
+Check for inconclusive errors also in the
+preferences to see them. An extra
+Inconclusive column then marks them. Expect more false warnings among
+them.
+
+
Where to start
+
+
The first analysis of a large project can give many warnings. Some
+tips:
+
+
Start with errors, then warnings. Use the
+ severity buttons in the toolbar to hide the
+ other severities.
+
Look at the information messages early. They are not bugs, but they
+ tell you about problems in the configuration, such as headers that can
+ not be found. Fixing them makes the other results better.
+
Sort by the Id column to handle one kind of warning at a time. If
+ one kind of warning is wrong everywhere, it may be a configuration
+ problem.
+
Use the quick filter to find warnings about a function, a variable or a
+ file.
+
With a build dir, sort by Since date to see the newest warnings
+ first. New warnings are the easiest to fix, because the code is fresh in
+ your mind.
Double-click a warning, or select it and press Enter, to open the file in
+your default editor at the line of the warning. You can also right-click a
+warning and choose an editor at the top of the context menu.
+
+
Editors are configured in the
+preferences on the Applications tab. On some systems the GUI finds
+installed editors automatically.
+
+
If the file is not found, for example because the results were loaded from
+an XML file created on another computer, the GUI asks you to select the
+folder where the file is.
+
+
+
The context menu
+
+
Right-click one or more selected warnings to get the context menu. Select
+several warnings with Ctrl+click or Shift+click.
+
+
+
+
+
Command
Description
+
(editors)
Open the file of the warning in this editor.
+
Recheck N file(s)
Analyze the selected files again. Select
+ file items (the top level items in the list) to enable this. Useful after
+ you have fixed something in a file. Not available while an analysis is
+ running or for results loaded from an XML file.
+
Copy
Copy the selected warnings, notes or file names to the
+ clipboard as text, in the format
+ file:line:column:severity: summary [id], followed by one line
+ for each note. Paste it in a bug report or a chat.
+
Hide N result(s)
Hide the selected warnings until the next
+ analysis. See Hiding warnings.
+
Hide all with id
Hide all warnings with the same id as the
+ selected warning, until the next analysis.
+
Suppress selected id(s)
Suppress all warnings with the ids of
+ the selected warnings, in all files. The suppression is saved in the
+ project file, so the warnings are not reported again. See
+ Hiding and suppressing warnings.
+
Open containing folder
Open the folder of the file in your
+ file manager.
+
Tag
Give the selected warnings a tag. Only shown if the
+ project has tags. See Tagging warnings.
+
+
+
Analyzing again
+
+
After you have changed the code:
+
+
Right-click a file in the results list and choose Recheck to
+ analyze just that file again.
+
Use Reanalyze all files in the toolbar to analyze everything again.
+ With a build dir only changed files are analyzed again, so this is
+ fast.
+
Use Analyze > Reanalyze modified files (Ctrl+R) to analyze only
+ the files that changed since the last analysis. See
+ The main window for when this is
+ available.
-
diff --git a/gui/help/mainwindow.html b/gui/help/mainwindow.html
new file mode 100644
index 00000000000..d12fe51c787
--- /dev/null
+++ b/gui/help/mainwindow.html
@@ -0,0 +1,293 @@
+
+
+The main window
+
+
+
+
+
The main window
+
+
This page describes the parts of the main window and every command in the
+menus and toolbars.
+
+
+
+
From top to bottom the main window has:
+
+
the menu bar,
+
the toolbars: the standard toolbar, the severity toolbar and the
+ quick filter,
+
the progress bar, which is shown while an analysis is running,
+
the results list with all warnings,
+
the lower panel with two tabs: Warning Details (the message
+ and the source code of the selected warning) and Analysis Log.
+
+
+
The title bar shows the name of the project file that is open. You can drag
+the bar between the results list and the lower panel to give more space to
+either of them. The GUI remembers the window size, the column widths and the
+position of the bar.
+
+
The results list
+
+
The results are grouped by file. Each file is a top level item; click the
+arrow to expand it, or use View > Expand all. Each warning in a file
+can have child items called notes, which describe the path through the
+code that leads to the problem.
+
+
The columns are:
+
+
+
Column
Meaning
+
File
The file of the warning. By default the path is shown
+ relative to the project (see Show full path of files in the
+ preferences and Root path in the
+ project file).
+
Line
The line number.
+
Severity
error, warning, style, performance, portability,
+ information, or note for the notes. See
+ Severities. The icon in the File column
+ shows the severity too.
+
Classification, Level, Guideline, Rule
Only shown when a
+ compliance report type (Misra, Cert, Autosar) is selected in
+ View > Report. See Report
+ types.
+
Inconclusive
Shown when inconclusive warnings are enabled in
+ the preferences. Marks warnings where Cppcheck is not sure.
+
Summary
A short description of the problem.
+
Id
The id of the checker message, for example
+ nullPointer. You need it for suppressions. The column can be
+ turned off in the preferences.
+
Since date
The date the warning was first seen. Only
+ available for projects with a build dir; the date is kept from analysis
+ to analysis as long as the warning is still there. This makes it easy to
+ see which warnings are new.
Click a column header to sort by that column. Drag the edge of a column
+header to change its width.
+
+
Double-click a warning, or select it and press Enter, to open the file in
+your default editor at the line of the warning. You must configure the editor
+first in the preferences.
+
+
Right-click warnings to get a context menu. See
+The context menu.
+
+
Warning Details and the code view
+
+
When you select a warning, the Warning Details tab shows the id, the
+CWE number and the full message. Some messages are longer than the summary in
+the list and explain the problem in more detail. If the file was included from
+another file, it also shows which file included it.
+
+
Below the message, the code view shows the source file with the line of the
+warning highlighted. Select a note to see the line of the note. The code view
+is read only; use your editor to change the code. The colors of the code view
+can be changed in the preferences.
+
+
Analysis Log
+
+
The Analysis Log tab shows which files were analyzed and with which
+configuration: the preprocessor defines, undefines, include paths and
+platform. Look here if the results are not what you expected, for example to
+check that the right include paths are used. Right-click the log to clear it
+or copy one or all entries.
+
+
If Show internal warnings in log is enabled in the preferences,
+internal debug warnings are shown here too.
+
+
If a critical error happens, for example a syntax error that stops the
+analysis of a file, a red message is shown above the results list.
+
+
Toolbars
+
+
+
+
The standard toolbar (left) has, from left to right: Open Project
+File, Save results to file, Reanalyze all files,
+Stop, Clear results, Preferences and About. These
+commands are described with the menus below.
+
+
The severity toolbar (right) has one toggle button for each
+severity: error, warning, style, portability, performance and information.
+When a button is pressed, warnings with that severity are shown. Release it to
+hide them. The last two buttons show or hide results from Cppcheck itself and
+results from Clang tools (Clang-Tidy). When you select a compliance report
+type, the buttons change to the classifications of that standard, for example
+Mandatory, Required and Advisory for Misra.
+
+
The quick filter field shows only warnings that contain the text you
+type. The text is searched (ignoring case) in the summary, the message, the id,
+the classification and the files and texts of the notes. Click the small cross
+in the field to clear the filter.
+
+
+
+
The toolbars can be turned on and off with View > Toolbars.
+
+
The File menu
+
+
+
+
+
Command
Description
+
Open XML...
Open a results file in XML format that was saved
+ earlier by the GUI or created with the command line tool
+ (cppcheck --xml ...). The current results are cleared. See
+ Opening results.
+
New Project File...
Create a new project file. First you
+ choose the file name, then the project
+ file dialog opens. When you click OK the analysis starts.
+
Open Project File...
Open an existing project file
+ (*.cppcheck). If the project has a build dir with results
+ from an earlier analysis, those results are shown. Otherwise the analysis
+ starts.
+
Edit Project File...
Change the settings of the open project.
+ When you click OK, the project file is saved and the code is analyzed
+ again.
+
Close Project File
Close the project and clear the results.
+ You must close the project before you can analyze files or folders
+ without a project.
+
(recent projects)
The project files you opened recently.
+ Click one to open it.
+
Save results to file...
Save the results as XML, text or CSV.
+ See Saving results.
Close the GUI. If an analysis is running, you are asked
+ if you want to stop it.
+
+
+
The Edit menu
+
+
+
+
+
Command
Description
+
Clear results
Remove all results from the list. If the
+ project has a build dir, the cached analysis results in the build dir are
+ deleted too, so the next analysis analyzes every file from scratch.
Show or hide the standard toolbar, the severity
+ toolbar (Categories) and the quick filter.
+
Report
Choose how warnings are classified: Normal
+ (Cppcheck severities) or according to a coding standard: Autosar, Cert C,
+ Cert C++, Misra C, Misra C++ 2008 or Misra C++ 2023. See
+ Report types.
+
Show errors ... Show information
The same as the buttons in
+ the severity toolbar.
+
Check all / Uncheck all
Show or hide all severities at
+ once.
+
Collapse all / Expand all
Collapse or expand all files and
+ warnings in the results list.
+
Show hidden
Show the warnings again that you have hidden with
+ the context menu. Only enabled when there are hidden warnings.
+
Show Scratchpad...
Open the
+ scratchpad, where you can type or paste code and analyze it.
+
Statistics
Show statistics
+ about the project and the last analysis.
+
Library Editor...
Open the
+ library editor to create or change a library configuration (.cfg)
+ file.
+
Thread Details
Show what each analysis thread is doing right
+ now. See Thread details.
+
+
+
The Analyze menu
+
+
+
+
+
Command
Description
+
Files...
Analyze one or more files without a project. You can
+ also select a compile database, a Visual Studio solution or project, or a
+ Borland C++ Builder 6 project here. See
+ Analyzing without a project.
+
Directory...
Analyze all source files in a folder and its
+ subfolders, without a project.
+
Reanalyze modified files
Analyze again only the files that
+ changed since the last analysis in this session, including files that
+ include a changed header. This works for files and folders you selected
+ with Files... or Directory... and for projects that list
+ their paths in the project file. For projects that import a compile
+ database or a Visual Studio project, use Reanalyze all files
+ instead.
+
Reanalyze all files
Analyze all files again. For a project with
+ a build dir, unchanged files are not analyzed again; their cached results
+ are used.
+
Reanalyze and check library
Analyze the project again and
+ report functions and types for which Cppcheck has no configuration. Use it
+ to find out what is missing in your library configurations. See
+ Checking the library
+ configuration.
+
Check configuration (defines, includes)
Analyze the project
+ again and report problems in the configuration, such as include files
+ that can not be found. Use it when you set up a new project.
+
Stop
Stop the running analysis (Esc). Results found so far are
+ kept.
+
C standard / C++ standard
The language standard used for
+ files when the standard is not known in another way (for example from an
+ imported project).
+
Auto-detect language / Enforce C++ / Enforce C
By default the
+ language of each file is chosen from its file extension: .c
+ files are C and .cpp, .cc, .cxx and
+ similar are C++. Choose Enforce C++ or Enforce C to analyze
+ all files as that language, for example if your C++ code is in
+ .c files.
+
Native ... Windows 64-bit
The platform to use when the project
+ does not choose a platform, or when you analyze without a project.
+ Native is the platform you run Cppcheck on. See
+ Platform.
+
+
+
The Help menu
+
+
+
+
+
Command
Description
+
Contents
Open this help (F1).
+
License...
Show the license of Cppcheck (GNU GPL v3).
+
Authors...
Show the people who have contributed to
+ Cppcheck.
Cppcheck is an analysis tool for C/C++ code. Unlike C/C++ compilers
- and many other analysis tools, it doesn't detect syntax errors. Cppcheck
- only detects the types of bugs that the compilers normally fail to detect.
- The goal is no false positives.
Supported code and platforms:
You can check non-standard code that includes various compiler
- extensions, inline assembly code, etc.
Cppcheck should be compilable by any C++ compiler that handles
- the latest C++ standard.
Cppcheck should work on any platform that has sufficient cpu and
- memory.
Accuracy
Please understand that there are limits of Cppcheck. Cppcheck is
- rarely wrong about reported errors. But there are many bugs that it
- doesn't detect.
You will find more bugs in your software by testing your software
- carefully, than by using Cppcheck. You will find more bugs in your
- software by instrumenting your software, than by using Cppcheck. But
- Cppcheck can still detect some of the bugs that you miss when testing and
- instrumenting your software.
There is no command to exclude a file or folder from checking. But
- you can exclude a file or folder by being more careful when including
- files and folders in the checking.
Imagine for example that the folder "src" contain the folders "a",
- "b" and "c". To exclude "c" this command can be used:
cppcheck src/a src/b
All files under "src/a" and "src/b" are then checked.
With --enable=style you enable most
- warning, style and
- performance messages.
Here is a simple code example:
void f(int x)
-{
- int i;
- if (x == 0)
- {
- i = 0;
- }
-}
There are no bugs in that code so Cppcheck won't report anything
- by default. To enable the stylistic messages, use the --enable=style
- command:
cppcheck --enable=style file3.c
The output from Cppcheck is now:
Checking file3.c...
-[file3.c:3]: (style) Variable 'i' is assigned a value that is never used
-[file3.c:3]: (style) The scope of the variable i can be reduced
By default Cppcheck will check all preprocessor configurations
- (except those that have #error in them). This is the recommended
- behaviour.
But if you want to manually limit the checking you can do so with
- -D.
Beware that only the macros, which are given here and the macros
- defined in source files and known header files are considered. That
- excludes all the macros defined in some system header files, which are by
- default not examined by cppcheck.
The usage: if you, for example, want to limit the checking so the
- only configuration to check should be "DEBUG=1;__cplusplus" then something
- like this can be used:
Checking gui/test.cpp...
-gui/test.cpp,31,error,memleak,Memory leak: b
-gui/test.cpp,16,error,mismatchAllocDealloc,Mismatching allocation and deallocation: k
Chapter 6. Suppressions
If you want to filter out certain errors you can suppress these.
- First you need to create a suppressions file. The format is:
The error id is the id that you want to suppress.
- The easiest way to get it is to use the --xml command
- line flag. Copy and paste the id string from the xml
- output.
Here is an example:
memleak:file1.cpp
-exceptNew:file1.cpp
-uninitvar
You can then use the suppressions file:
cppcheck --suppressions suppressions.txt src/
-
Chapter 7. Exception safety
Cppcheck has a few checks that ensure that you don't break the basic
- guarantee of exception safety. It doesn't have any checks for the strong
- guarantee yet.
[except2.cpp:7]: (error) Throwing exception in invalid state, p points at deallocated memory
Chapter 8. Html report
You can convert the xml output from cppcheck into a html report.
- You'll need python and the pygments module
- (http://pygments.org/) for this to work. In the Cppcheck source
- tree there is a folder "htmlreport" that contains a script that transforms
- a Cppcheck xml file into html output.
This command generates the help screen:
htmlreport/cppcheck-htmlreport -h
The output screen says:
Usage: cppcheck-htmlreport [options]
-
-Options:
- -h, --help show this help message and exit
- --file=FILE The cppcheck xml output file to read defects from.
- Default is reading from stdin.
- --report-dir=REPORT_DIR
- The directory where the html report content is written.
- --source-dir=SOURCE_DIR
- Base directory where source code files can be found.
The project files are used to store project specific settings.
- These settings are:
include folders
preprocessor defines
It isn't recommended to provide the paths to the standard C/C++
- headers - Cppcheck has internal knowledge about ANSI C/C++ and it isn't
- recommended that this known functionality is redefined. But feel free to
- try it.
As you can read in chapter 3 in this manual the default is that
- Cppcheck checks all configurations. So only provide preprocessor defines
- if you want to limit the checking.
Number of threads Number of threads to use in analysis. Each
-thread checks its own source file.
-
-
Force checking of all #ifdef configurations Cppcheck try to check
-all code and will therefore guess different preprocessor configurations. The
-maximum number of configurations that is checked is 14 by default.
-
-
Show full path of files Show the full paths in the results.
-
-
Show "No errors found" message when no errors found
-If you want to get a message box about this.
-
-
Display error id column "Id" Show error id in results
-
-
Enable inline suppressions You can suppress warnings with comments.
-See the Cppcheck manual (https://cppcheck.sourceforge.io/manual.pdf) for more
-information about inline suppressions.
-
-
Check for inconclusive errors also When full analysis of the code
-can not determine if there should be a warning or not, it is inconclusive.
-Normally Cppcheck does not warn then.
-
-
Show statistics on check completion Show statistics in a window
-when analysis finish.
-
-
Show internal warnings in log Internal warnings (for debugging) is
-shown in the Analysis log.
-
-
Applications Configure external editor to open from context menu
-when you right click on a warning.
-
-
Save all errors when creating report If hidden warnings should be
-saved or not.
-
-
Save full path to files in report If you use Root path the
-warnings on the screen will not have the full path.
-
-
Language Configure language to use for GUI.
-
-
Python binary To be able to execute addons, Cppcheck needs to know
-where python is. Unless you configure something, Cppcheck will try to execute
-python in your PATH.
-
-
MISRA rule texts Only needed if you want to use the MISRA addon.
-Cppcheck is not legally allowed to distribute the MISRA rule texts and these
-must be provided by users. The MISRA rule texts are proprietary. An example
-rule text file:
-
Appendix A Summary of guidelines
-Rule 1.1
-Text of rule 1.1
-Rule 1.2
-Text of rule 1.2
-
-
-
Clang path The path to clang binary. If no path is provided
-then system PATH is used.
-
-
Visual studio headers If you want to use the Visual Studio headers
-in the analysis you can provide the path(s) here. Hint: Open a visual studio
-command prompt and type SET INCLUDE. Then copy/paste the paths.
-
-
Code editor style The visual theme to use for the code editor that
-is used when you investigate results.
+
Open the preferences with Edit > Preferences or the preferences
+button in the toolbar. The preferences are your personal settings; they are
+stored for your user account, not in the project file. Click OK to
+apply the changes.
+
+
Some settings exist both here and in the project file, for example
+Enable inline suppressions. When a project is open, the project
+setting is used.
+
+
General
+
+
+
+
+
Option
Description
+
Number of threads
How many files are analyzed at the same time.
+ Each thread analyzes its own file. Max count shows the number of
+ processor cores of your computer, which is the highest value you can
+ enter. More threads make the analysis faster, but use more memory. A value
+ equal to the number of cores is usually a good choice.
+
Force checking all #ifdef configurations
By default Cppcheck
+ analyzes at most 12 preprocessor configurations of each file. With this
+ option all configurations are analyzed. This finds more bugs in code with
+ many #ifdefs, but can be very slow. See
+ Defines.
+
Show full path of files
Show the full path of files in the
+ results list, instead of the path relative to the project.
+
Show "No errors found" message when no errors found
Show a
+ message box when an analysis finishes without warnings.
+
Display error Id in column "Id"
Show the Id column in
+ the results list. The id is needed for suppressions.
+
Enable inline suppressions
Allow comments like
+ // cppcheck-suppress nullPointer in the code to suppress
+ warnings. Only used when no project is open; projects have their own
+ setting. See Inline
+ suppressions.
+
Check for inconclusive errors also
Also report warnings where
+ Cppcheck is not sure that there is a problem. These give more findings, but
+ also more false warnings. An Inconclusive column is added to the
+ results list.
+
Show statistics on check completion
Open the
+ statistics dialog when an analysis
+ finishes.
+
Check for updates
When the GUI starts, check on the Cppcheck
+ web site if there is a newer version. If there is, a message is shown at
+ the top of the main window. This needs an internet connection; nothing
+ else is sent.
+
Show internal warnings in log
Show debug warnings from the
+ analysis in the Analysis Log. Only useful if you develop Cppcheck or
+ investigate a problem with the analysis.
+
+
+
+
Applications
+
+
+
+
The editors (or other programs) that you can open a warning in. They are
+shown at the top of the context menu of the results list. The default
+application is used when you double-click a warning or press Enter.
+
+
On some systems the GUI finds installed editors automatically and adds them
+to the list.
+
+
+
Add...: add an application.
+
Edit...: change the selected application.
+
Remove: remove the selected application.
+
Set as default: make the selected application the default one.
+
+
+
+
+
For each application you enter a name for the menu, the executable and the
+command line parameters. These texts in the parameters are replaced when the
+application is started:
+
+
+
Text
Replaced with
+
(file)
The file name of the warning
+
(line)
The line number
+
(message)
The warning message
+
(severity)
The severity
+
+
+
Examples for some common editors:
+
+
+
Editor
Executable
Parameters
+
Visual Studio Code
code
+
--goto (file):(line)
+
Kate
kate
+
-l(line) (file)
+
Qt Creator
qtcreator
+
-client (file):(line)
+
Notepad++
notepad++.exe
+
-n(line) (file)
+
Vim (in a terminal)
xterm
+
-e vim +(line) (file)
+
Emacs
emacs
+
+(line) (file)
+
+
+
Reports
+
+
+
+
+
Option
Description
+
Save all errors when creating report
Also save hidden warnings
+ when you save the results to a file. By default only the visible warnings
+ are saved.
+
Save full path to files in reports
Save the full path of each
+ file. By default the path relative to the project (or the root
+ path) is saved.
The language of the user interface. The GUI is translated to several
+languages; select one and click OK. This help is only available in
+English.
+
+
+
Addons
+
+
+
+
+
Option
Description
+
Python binary
The Python interpreter used to run addons. Leave
+ it empty to use the Python found in the PATH. If the file you enter does
+ not exist, a red warning is shown.
+
MISRA rule texts file
A text file with the MISRA C rule texts,
+ used by the Misra addon to show the rule texts in the warnings. Cppcheck is
+ not allowed to distribute the rule texts, so you must create the file from
+ your copy of the MISRA C document. See
+ Addons for the format. This is
+ the same setting as MISRA rule texts in the project dialog.
+
+
+
+
Clang
+
+
+
+
+
Option
Description
+
Clang path
The folder where clang and
+ clang-tidy are installed. Used by the Clang parser and by
+ Clang-Tidy (see Analysis and
+ External tools in the project
+ dialog). Leave it empty to search the system PATH. On Windows, Clang is
+ also found in C:\Program Files\LLVM\bin.
+
Visual Studio headers
Paths to the Visual Studio headers,
+ separated by semicolons. Clang needs them to parse code that uses the
+ Visual Studio libraries. To get the paths, open a Visual Studio command
+ prompt, type SET INCLUDE and copy/paste the paths.
+
+
+
+
Code Editor
+
+
+
+
The colors of the code view in the lower part of the main window.
+
+
System Style: use the colors of your desktop theme.
+
Default Light Style: dark text on a light background.
+
Default Dark Style: light text on a dark background.
+
Custom: your own colors. Click Edit... to choose the
+ foreground and background colors of the editor, the highlighted line and
+ the line numbers, and the color and font weight of keywords, class names,
+ strings, comments and symbols. A preview shows the result.
A project file (*.cppcheck) stores everything Cppcheck needs to
+know to analyze your code: which files to analyze, include paths, defines,
+libraries, platform, analysis options, suppressions, tags and addons. It is
+an XML file, so you can put it in version control and share it with your
+team. Paths in the project file are relative to the folder of the project
+file.
-
Paths and defines
+
Create a project with File > New Project File... and change it
+with File > Edit Project File.... Both open the Project file
+dialog described on this page. When you click OK the project file is
+saved and the analysis starts. Click Cancel to close the dialog without
+saving.
-
It is recommended to import a project file.
+
The dialog has five tabs:
+
+
Paths and Defines: what code to analyze and the
+ preprocessor settings,
Analysis: the build dir, the parser and options
+ that control how deep the analysis is,
+
Warning options: root path, tags, excluded
+ files and suppressions,
+
Addons: addons, coding standards and external
+ tools.
+
-
Import project
+
+
Paths and Defines
+
+
+
+
On this tab you tell Cppcheck what code to analyze. You can either import a
+project from your build system, or enter the paths, defines and include paths
+yourself. Importing is recommended: you get the exact include paths and
+defines that the compiler uses, and you do not have to update the Cppcheck
+project when the build changes.
+
+
+
Import Project
+
+
Click Browse... to select the project to import. Supported
+formats:
+
+
+
Format
Notes
+
Compile database compile_commands.json
+
Contains the exact compiler command line of every source file. CMake
+ writes it when you configure with
+ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON. Meson and Ninja write it
+ too, and for other build systems (for example make) you can create it
+ with tools such as Bear. When a compile database is imported, each
+ file is analyzed with just the configuration given on its command line.
+
Visual Studio *.sln, *.slnx,
+ *.vcxproj
+
Cppcheck reads the source files, include paths and defines of each
+ configuration (such as Debug|Win32 or
+ Release|x64).
+
Borland C++ Builder 6 *.bpr
+
+
+
+
Click the brush button next to the field to remove the import.
+
+
When a project is imported, the Paths, Include Paths and
+Undefines fields are disabled because the information comes from the
+imported project. Defines and Include file can still be used;
+they are added to what the imported project says.
+
+
Analyze all Visual Studio configurations and Selected VS
+Configurations: only enabled when a Visual Studio solution or project is
+imported. A Visual Studio project usually has several configurations, and
+analyzing all of them takes time. Check Analyze all Visual Studio
+configurations to analyze every Debug and Release configuration.
+Otherwise check the configurations you want in the list. If you analyze
+several configurations of a file, warnings that are the same in all of them
+are only reported once.
+
+
Paths
+
+
The folders and files to analyze (only when no project is imported).
+Click Add... to add a folder, select a path and click Edit to
+change it, or Remove to remove it. All C and C++ source files in the
+folders and their subfolders are analyzed (.c,
+.cpp, .cxx, .cc, .c++,
+.tpp, .txx, .ipp, .ixx).
+Headers are analyzed when they are included from a source file.
+
+
To leave out some files or folders, use Exclude source
+files on the Warning options tab.
+
+
+
Defines
+
+
Preprocessor macros that are defined for the analysis, like the
+-D option of a compiler. Separate them with semicolons, and give a
+value with =:
+
+
DEBUG;VERSION=5;WORD=int
+
+
To understand when you need defines, you need to know how Cppcheck handles
+preprocessor conditions. A compiler only sees one configuration of your code.
+Cppcheck instead tries to analyze all configurations. Look at this code:
+
+
#ifdef A
+ code1
+#endif
+#ifdef B
+ code2
+#endif
-Project to import. Cppcheck will get:
+
Cppcheck automatically analyzes the code both with and without
+A and B defined, so bugs in both code1
+and code2 are found. You normally do not have to set defines. Use
+them when:
-
What files to check
-
Preprocessor defines
-
Preprocessor include paths
-
Language standard if set
+
a macro must always be defined for the code to make sense, for example
+ a macro that your build system always sets,
+
a macro has a value that matters, for example
+ BUFFER_SIZE=1024,
+
Cppcheck reports a syntax error because a macro it does not know is used
+ in a way that only works when the macro is defined.
-
Paths (If you do not import project)
+
By default at most 12 configurations are analyzed for each file. If there
+are more, an information message tells you. Enable Force checking
+all #ifdef configurations in the preferences
+to analyze all of them, which can take a long time.
-
What paths to check.
+
Undefines
-
Defines (If you do not import project)
+
Macros that must never be defined, like the -U option of a
+compiler. Separate them with semicolons:
-
Cppcheck automatically checks the code with different preprocessor
-configurations.
+
A;C
-
#ifdef A
-code1
-#endif
-#ifdef B
-code2
-#endif
+
In the example above, if A is undefined, Cppcheck does not
+analyze the configuration where A is defined. Use this for
+configurations that are never used, for example code for a platform you do not
+support. It makes the analysis faster and avoids warnings for code you do not
+care about.
-
Cppcheck will automatically perform analysis both when A is defined and B is
-defined. So any bugs in both code1 and code2 will be detected.
+
+
Include Paths
-
If you want to configure that A will always be defined in Cppcheck analysis
-you can do that here.
+
The folders where Cppcheck looks for header files that are included with
+#include "..." or #include <...>, like the
+-I option of a compiler. Folders are searched in the order of the
+list; use Up and Down to change the order.
-
Defines are separated by semicolon. So you can for instance write:
+
Add the folders of your own headers and of the headers of 3rd party
+libraries that are part of your project. Cppcheck needs them to know your
+types, macros, functions and classes. Without them, some bugs can not be found
+and you may get false warnings.
-
A;B=3;C
+
Do not add the folders of the standard library and system headers
+(for example /usr/include or the Visual Studio include
+folders). Cppcheck has built-in knowledge of the standard library and of the
+libraries you select on the Types and Functions tab, and analyzing the
+system headers makes the analysis slow and can cause wrong results. That is
+why the missingIncludeSystem information messages say that Cppcheck does
+not need the standard headers.
-
Undefines (If you do not import project)
+
Include file
-
Cppcheck automatically checks the code with different preprocessor
-configurations.
+
A header file that is included before every source file, like the
+-include option of GCC or /FI of Visual Studio. Use
+it if your build forces a header to be included, for example a configuration
+header with macros that all files need.
-
#ifdef A
-code1
-#endif
-#ifdef B
-code2
-#endif
+
+
Types and Functions
-
Cppcheck will automatically perform analysis both when A is defined and B is
-defined. So any bugs in both code1 and code2 will be detected.
+
-
If you want to configure that A is never defined in Cppcheck analysis you
-can do that here.
+
+
Platform
-
Undefines are separated by semicolon. So you can for instance write:
+
The platform decides the sizes of the basic types (short,
+int, long, long long, pointers,
+size_t, wchar_t) and whether char is
+signed. Cppcheck uses this, for example, to find integer overflows, buffer
+overruns with sizeof and portability problems.
-
A;C
+
Choose one of the built-in platforms:
+
+
Native: the computer you run Cppcheck on,
+
Windows 32-bit ANSI, Windows 32-bit Unicode,
+ Windows 64-bit,
+
Unix 32-bit, Unix 64-bit.
+
-
Include paths (If you do not import project)
+
For embedded targets and other special platforms, choose one of the
+platform files (*.xml) in the list, for example
+avr8.xml, arm32-wchar_t2.xml or
+pic16.xml. You can write your own platform file and put it in the
+folder of the project file; it then appears in the list. See the Cppcheck
+manual for the format.
-
Specify include paths.
+
If you leave the platform empty, the platform selected in the
+Analyze menu of the main window is used.
-
Types and Functions
+
+
Libraries
-
Cppcheck uses the Platform setting to determine size of
-short/int/long/pointer/etc.
+
Check the libraries that your code uses. Each library has a configuration
+file (.cfg) that describes its functions, types and macros, for
+example:
+
+
which functions allocate and free memory or other resources, so that
+ leaks and double frees can be found,
+
which arguments must not be null or uninitialized, and which values are
+ valid,
+
which functions never return (like exit()),
+
which return values must be used,
+
format strings, buffer sizes and so on.
+
-
Check the libraries that you use in the Libraries listbox.
+
The standard library (std.cfg) is always used. Some
+commonly used libraries are posix (Linux and Unix APIs),
+windows (Windows API), gnu, qt, boost,
+wxwidgets, gtk, sdl, opengl, openssl,
+sqlite3 and googletest.
+
If your code uses a library that is not in the list, or your own library,
+you can write a configuration file for it. Put the .cfg file in
+the same folder as the project file and it appears in the list. You can create
+and edit configuration files with the library
+editor. Use Analyze > Reanalyze and check library to find
+functions that are not configured.
+
+
Analysis
+
+
+
Cppcheck build dir
-
This is a work-folder that Cppcheck uses. Each Cppcheck project should have
-a separate build dir. It is used for:
+
A work folder for Cppcheck. Each project needs its own build dir; do not
+share one build dir between projects. A new project automatically gets
+<name>-cppcheck-build-dir next to the project file. If the
+folder does not exist when the analysis starts, the GUI asks if it should
+create it.
+
+
The build dir is optional, but strongly recommended. It is used for:
-
whole program analysis
-
debug output
-
faster analysis (if a source file has changed check it, if source file is
- not changed then reuse old results)
-
statistics
+
Incremental analysis. Cppcheck stores the results of each file. When
+ you analyze again, files that have not changed (including the headers they
+ include) are not analyzed again; their stored results are used. This makes
+ the second analysis of a large project much faster.
+
Whole program analysis. Some checks need information from all
+ files, for example finding functions that are never called
+ (unusedFunction) or bugs where a value is passed from one file to a
+ function in another file. These checks are only run when there is a build
+ dir.
+
Addons. Addons (Misra, Y2038, Thread safety) and Clang-Tidy need a
+ build dir.
+
Results history. The results of the last analysis are saved
+ (lastResults.xml) and shown when you open the project. The
+ Since date column shows when each warning was first seen.
+
Statistics of previous analyses.
+
Do not put the build dir in version control. Edit > Clear
+results deletes the stored results in the build dir.
+
Parser
-
It is in general recommended to use Cppcheck parser. However you can choose
-to use Clang parser; Clang will be executed with a command line flag that tells
-it to dump its AST and Cppcheck will read that AST and convert it into a
-corresponding Cppcheck AST and use that.
+
Cppcheck (built in) is recommended. Cppcheck has its own parser that
+works well even if the code can not be compiled, if headers are missing or if
+the code uses compiler extensions.
-
Analysis
+
Clang (experimental) uses Clang to parse the code. Clang is run with
+a flag that makes it write its abstract syntax tree (AST); Cppcheck reads this
+AST, converts it and runs its checks on it. Clang must be installed (see
+Clang path in the preferences), and the
+code must be correct enough for Clang to accept it. This is experimental and
+not all checks work as well as with the built in parser.
-
Configure what kind of analysis you want.
+
Check level
-
The Normal analysis is recommended for most use cases. Especially if
-you use Cppcheck in CI.
+
How much time Cppcheck may spend on the analysis. A higher level finds more
+bugs but takes longer.
+
+
Reduced: a limited and fast analysis, for when you want results
+ right away, for example while you are writing code.
+
Normal: the default. Meant for normal analysis in CI; it should
+ finish in reasonable time.
+
Exhaustive: a deeper analysis of the control flow and of values,
+ for nightly builds and similar. It can take much longer; up to 10 times
+ the compilation time is normal.
+
-
The Bug hunting can be used if you really want to find a bug in your
-code and can invest time looking at bad results and providing extra
-configuration.
+
Analysis
-
Limit analysis
+
Check that each class has a safe public interface: normally Cppcheck
+only considers the values that are actually passed to a function. With this
+option, Cppcheck assumes that the arguments of public member functions can
+have any value, and warns if a function does not handle some value
+safely, even if no caller passes that value today. Use it if you want your
+classes to be robust against all possible use. It gives more warnings, and you
+must decide for each of them if it matters.
-
You can turn off checking of headers. That could be interesting if Cppcheck
-is very slow. But normally, you should check the code in headers.
+
Limit analysis
-
It is possible to check the code in unused templates. However the Cppcheck
-AST will be incomplete/wrong. The recommendation is that you do not check
-unused templates to avoid wrong warnings. The templates will be checked
-properly when you do use them.
+
Check code in headers: should normally be on. If you turn it off,
+Cppcheck does not report warnings in the code of header files and skips some
+analysis of it. This can make the analysis much faster for code with large
+headers, but you will miss bugs in headers.
-
Max CTU depth: How deep should the whole program analysis be. The risk with
-a "too high" value is that Cppcheck will be slow.
+
Check code in unused templates: should normally be on. If it is off,
+templates that are never instantiated are not analyzed. Cppcheck can not know
+the template arguments in unused templates, so the analysis of them is less
+precise. Templates that are used are always analyzed with the actual template
+arguments.
-
Max recursion in template instantiation: Max recursion when Cppcheck
-instantiates templates. The risk with a "too high" value is that
-Cppcheck will be slow and can require much memory.
+
Max CTU depth: how deep the whole program analysis follows function
+calls between files (CTU means "cross translation unit"). A higher value finds
+bugs where values pass through more function calls, but makes the analysis
+slower. The default is 2.
+
Max recursion in template instantiation: how deep recursive templates
+are instantiated. Code that uses template metaprogramming can need a higher
+value. A high value can make the analysis slow and use a lot of memory. The
+default is 100.
+
Warning options
+
+
+
Root path
-
The root path for warnings. Cppcheck will strip away this part of the path
-from warnings. For instance if there is a warning in
-
../myproject/foo/bar/file.cpp
and the root path is
-
../myproject/foo
then the path for the warning will be
-
bar/file.cpp
.
+
File paths in warnings are shown relative to this path. If it is empty, the
+folder of the project file is used. For example, if a warning is in
+
../myproject/foo/bar/file.cpp
+
and the root path is
+
../myproject/foo
+
then the warning is shown for
+
bar/file.cpp
-
Warning Tags
+
Warning tags
-
Tags allow you to manually categorize warnings.
+
Your own names for categories of warnings, separated by semicolons, for
+example todo;false positive;fixed later. When you have entered
+tags, you can right-click warnings and give them a tag. See
+Tagging warnings. Only letters, digits and spaces
+are allowed in tag names.
+
Exclude source files
-
Excluded source files will not be analyzed by Cppcheck.
+
Files and folders that should not be analyzed. Use it for generated code,
+3rd party code that you do not maintain, test data and similar. Click
+Exclude folder... or Exclude file... to add entries, Edit
+to change the selected entry and Remove to remove it.
+
Excluding works for imported projects too. Note that an excluded header is
+still read when it is included from a source file that is analyzed, because
+Cppcheck needs it to understand that source file.
+
+
Suppressions
-
List of suppressions. These warnings will not be shown.
+
Warnings that should not be reported. Click Add to add a
+suppression; double-click a suppression to edit it, and click Remove to
+remove it.
-
Addons
+
+
+
A suppression has these fields:
+
+
Error ID (required): the id of the warning, for example
+ nullPointer. You can select it from the list or type it. Use
+ * to suppress all warnings (for example for some file).
+
File name (optional): only suppress the warning in this file. You
+ can use the wildcards * and ?, for example
+ src/generated/*.
+
Line number (optional): only suppress the warning on this line.
+ This is fragile: when the code changes, the line number changes too.
+ Inline suppressions are better for single warnings.
+
Symbol name (optional): only suppress warnings about this
+ variable, function or other symbol.
MISRA Ensure that the MISRA coding standard is followed. Please
-note you need to have a textfile with the misra rule texts to get proper
-warning messages. Cppcheck is not legally allowed to distribute the misra
-rule texts.
+
+
Addons
-
Clang-tidy Run Clang-tidy
+
+
+
Addons are extra checkers, written in Python, that run after the normal
+analysis. Addons need Python to be installed (see Python binary in the
+preferences) and the project must have a
+build dir. If an addon can not be found, it is shown as
+(Not found) and can not be selected.
+
+
Addons
+
+
Y2038: on many 32-bit systems time_t is a signed 32-bit
+integer that counts seconds since 1970. It overflows on 19 January 2038. This
+addon warns about code that uses such 32-bit time values.
+
+
Thread safety: warns about code that is not safe to use from several
+threads, for example local static variables and calls to functions that are
+not thread safe.
+
+
Coding standards
+
+
Misra C: checks the code against the MISRA C guidelines, a coding
+standard for safety critical C code. Open source Cppcheck has a Misra addon
+that does not cover all rules.
+
+
The MISRA rule texts are copyrighted and Cppcheck is not allowed to
+distribute them. Without them, Misra warnings only show the rule number. To
+get the rule texts in the warnings, buy the MISRA C document, copy the text of
+Appendix A "Summary of guidelines" to a text file, and select that file in
+MISRA rule texts. The file should look like this:
+
Appendix A Summary of guidelines
+Rule 1.1
+Text of rule 1.1
+Rule 1.2
+Text of rule 1.2
+
The rule text file is a user setting; it is not stored in the project
+file.
+
+
Misra C++, Cert C, Cert C++, Autosar and
+Safety profiles are only available in Cppcheck Premium. Cppcheck
+Premium also lets you choose the version of Misra C (2012, 2023 or 2025) and
+Misra C++ (2008 or 2023). For Cert C you can give the precision of
+int, which is used for rule INT35-C if the precision is not the
+same as the size.
+
+
Bug hunting (Premium)
+
+
A very deep analysis that tries to find all bugs of some kinds, at the cost
+of more false warnings and a longer analysis. Only available in Cppcheck
+Premium.
+
+
External tools
+
+
Clang-Tidy: also run Clang-Tidy on the code and show its warnings
+together with the Cppcheck warnings. Clang-Tidy must be installed. It is
+searched for in the Clang path from the
+preferences, or in the system PATH. If it is not found, the checkbox shows
+(not found). Use the Clang button in the severity toolbar to show
+or hide the Clang-Tidy results.
Select File > Save results to file... (Ctrl+S) or click the save
+button in the toolbar. Choose a file name and a format:
+
+
+
Format
Contents
+
XML files (*.xml)
All information about each warning: file,
+ line, column, severity, id, CWE, message, notes, tags and more. This is
+ the same XML format (version 2) as the command line tool writes with
+ --xml. The GUI can open it again, and many other tools, for
+ example CI systems and report generators, can read it. Use this format if
+ you want to keep the results.
+
Text files (*.txt)
One line per warning, in the classic
+ Cppcheck text format:
+ [src/buffer.c:9] -> [src/buffer.c:10]: (error) Array 'buf[16]'
+ accessed at index 16, which is out of bounds.
+ The locations of the notes are listed first, connected with
+ ->.
+
CSV files (*.csv)
One line per warning with the columns file,
+ line, severity, id and summary. Open it in a spreadsheet to sort, count
+ and filter warnings.
+
+
+
Two preferences on the Reports tab affect
+what is saved:
+
+
Save all errors when creating report: also save warnings that are
+ hidden. By default only the warnings you can see are saved, so you can use
+ the severity buttons, the quick filter and hiding to choose what goes in
+ the report.
+
Save full path to files in reports: save the full path of each
+ file instead of the path relative to the project.
+
+
+
+
Opening results
+
+
Select File > Open XML... to open a results file in XML format.
+The current results are cleared first. The file can come from the GUI or from
+the command line tool, for example from a CI job:
When results are opened from a file, the GUI needs to find the source files
+to show the code. If the paths in the file are relative, the GUI asks for the
+folder they are relative to when you select a warning. You can also give the
+folder on the command line with the -d option; see
+Command line options.
+
+
XML files in the old version 1 format can not be opened. Use
+--xml with a recent Cppcheck version to create files in the
+version 2 format.
+
+
For results opened from a file, the code can not be analyzed again with
+Recheck, because the GUI does not know how it was analyzed.
+
+
+
Printing
+
+
Select File > Print... (Ctrl+P) to print the results, or File
+> Print Preview... to see what will be printed first. The printout has
+one line per warning with the file, line, severity and summary. Like saved
+reports, only the warnings you can see are printed. You can print to a PDF
+file if your system has a PDF printer.
+
+
+
Statistics
+
+
Select View > Statistics to see statistics about the project and
+the last analysis. Enable Show statistics on check completion in the
+preferences to show the statistics
+automatically after each analysis.
+
+
The statistics dialog has these tabs:
+
+
Project
+
The root path of the project, the paths that are analyzed, the include
+paths, the defines and the undefines.
+
+
+
Previous Scan
+
The path that was analyzed, the number of files and how long the analysis
+took.
+
+
+
Statistics
+
The number of warnings of each severity, and the number of active
+checkers.
+
+
+
Checkers
+
A report of which checkers were active in the analysis and which were not.
+Use it to see what was checked, for example to document the analysis for a
+safety certification.
+
+
History
+
A chart of the number of warnings over time, read from the build dir of the
+project. Only available if the GUI was built with Qt Charts and the project
+has a build dir.
+
+
Click Copy to Clipboard to copy the statistics as text and HTML, for
+example to paste them in an e-mail or a document. Click Pdf Export to
+save them as a PDF file.
+
+
Note: the statistics count the warnings of the last
+analysis in this session. When you open a project with a build dir, the
+results of the previous session are shown, but they are not counted until you
+analyze again.
Every warning has a severity that tells you what kind of problem it
+is and how serious it is. The severity is shown in the Severity column
+and as an icon in the results list. Use the buttons in the severity toolbar,
+or the View menu, to show or hide each severity.
-
-
when code is executed there is some bad behavior (undefined behavior, leak)
+
error
-
warning
+
The code has a bug that causes bad behavior when it is executed: undefined
+behavior, a crash, a memory or resource leak, and similar. Errors should
+always be fixed.
-
-
when code is executed there might be undefined behavior
+
Examples: array index out of bounds (arrayIndexOutOfBounds),
+null pointer dereference (nullPointer), use of an uninitialized
+variable (uninitvar), memory leak (memleak), resource
+leak (resourceLeak), use after free
+(deallocuse), division by zero (zerodiv),
+mismatching allocation and deallocation (mismatchAllocDealloc).
point out possible mistakes, and suggest more defensive programming. Examples:
+
warning
+
+
The code can cause bad behavior in some situations, or there is code that is
+almost certainly not doing what the programmer wanted. Warnings should be
+looked at carefully; most of them are bugs.
+
+
Examples: a null pointer check after the pointer has already been used
+(nullPointerRedundantCheck), not checking the result of
+malloc() (nullPointerOutOfMemory), wrong argument type
+for a printf format (invalidPrintfArgType_sint), assignment in a
+condition where a comparison was meant, a member variable that is not
+initialized in a constructor (uninitMemberVar).
+
+
void f(struct S *s)
+{
+ int x = s->x;
+ if (!s) // warning: nullPointerRedundantCheck
+ return;
+}
+
+
style
+
+
The code is not wrong, but it can be improved: it is confusing, redundant,
+or not as safe as it could be. Style warnings often point to real bugs, for
+example a condition that is always true can mean that the wrong variable is
+used. They also suggest more defensive programming.
+
+
Examples:
+
+
unused variables, functions and code (unusedVariable,
+ unreadVariable, unusedFunction),
+
conditions that are always true or false
+ (knownConditionTrueFalse),
+
variables, parameters and member functions that can be const
+ (constVariable, constParameterPointer,
+ functionConst),
+
the scope of a variable can be reduced (variableScope),
+
duplicate code in if/else branches (duplicateBranch),
int f(int x)
+{
+ if (x > 0 && x > 10) // style: the first condition is redundant
+ return 1;
+ return 0;
+}
+
+
performance
+
+
Suggestions to make the code faster. They are based on common knowledge
+about what is slow. It is not certain that you get a measurable speed
+improvement by fixing them; it depends on how often the code runs.
+
+
Examples: passing a large object by value instead of by const reference
+(passedByValue), using it++ instead of
+++it for iterators (postfixOperator), inefficient use
+of STL containers.
The code may work on your platform but not on others: implementation
+defined behavior, 64-bit problems, or undefined behavior that happens to work
+"as you want" with your compiler.
+
+
Examples: storing a pointer in an int
+(AssignmentAddressToInteger), shifting a negative value, casting
+between incompatible pointer types (invalidPointerCast), assuming
+the size of a type.
+
+
Some portability warnings depend on the platform you have selected; see
+Platform.
+
+
information
+
+
Messages about the analysis itself, not about your code. If you get
+information messages, your code may be fine but the Cppcheck configuration can
+be improved. Fixing the configuration usually gives better results.
+
+
Examples:
-
unused code/variables/functions
-
conditions that are always true/false
-
constness
-
operator precedence
+
missingInclude: an include file of your project was not
+ found. Add the folder to the
+ include paths.
+
missingIncludeSystem: a standard or system header was not
+ found. This is normal and you do not need to do anything; Cppcheck has
+ built-in knowledge of the standard library. You can hide these with the
+ severity toolbar or suppress the id.
+
toomanyconfigs: a file has more preprocessor configurations
+ than Cppcheck analyzes by default. See
+ Defines.
+
normalCheckLevelMaxBranches: the analysis of a function was
+ limited. Use the Exhaustive check level to analyze it fully.
+
messages from Analyze > Reanalyze and check library about
+ functions that are not configured.
-
-
performance
+
note
-
-
Suggestions for making the code faster. These suggestions are only based on common knowledge. It is not certain you'll get any measurable difference in speed by fixing these messages.
+
Notes are not warnings. They are shown as child items of a warning and
+explain the path through the code that leads to it. See
+Investigating warnings.
-
portability
+
+
Report types
+
+
If you work with a coding standard, you can show the results classified
+according to that standard. Select the standard in View > Report:
+
+
+
+
+
Report
Classifications
Extra columns
+
Normal
The Cppcheck severities described above.
+
+
Autosar
Required, Advisory
+
Classification, Guideline
+
Cert C, Cert C++
L1, L2, L3 (the CERT priority levels; L1 is
+ the most severe)
Level, Rule
+
Misra C, Misra C++ 2023
Mandatory, Required, Advisory
+
Classification, Guideline
+
Misra C++ 2008
Required, Advisory, Document
+
Classification, Guideline
+
+
+
With a compliance report type:
+
+
Each warning is mapped to the guideline or rule of the standard that it
+ relates to. The Guideline or Rule column shows it, and the
+ Classification or Level column shows how strict the
+ guideline is.
+
The icons and the buttons in the severity toolbar follow the
+ classification: Mandatory and L1 use the error icon, Required and L2 the
+ warning icon, Advisory and L3 the style icon, and Document the
+ information icon.
+
Warnings that are not related to any guideline of the standard are not
+ shown.
+
-
-
portability warnings. Implementation defined behavior. 64-bit portability. Some undefined behavior that probably works "as you want". etc.
+
-
information
+
The report type only changes how results are shown; the analysis is the
+same. To check the code against the rules of a coding standard, enable the
+standard on the Addons tab of the
+project. Open source Cppcheck has a Misra C addon; checking the other
+standards needs Cppcheck Premium.
-
-
Configuration problems. If you get such output then your code is ok but your cppcheck configuration could be improved.
diff --git a/gui/help/standalone-analysis.html b/gui/help/standalone-analysis.html
index ad7fae5fe55..bf7cc0096b3 100644
--- a/gui/help/standalone-analysis.html
+++ b/gui/help/standalone-analysis.html
@@ -1,17 +1,125 @@
-
-Standalone analysis
+Analyzing without a project
+
-
Standalone analysis
+
Analyzing without a project
+
+
You can analyze code quickly without creating a project file. This is
+useful to try Cppcheck, or to look at a few files. For regular use a project
+is better: with a properly configured project Cppcheck knows your include
+paths, defines and libraries, and the analysis is more accurate. You also get
+features that need a project, such as saved suppressions, tags and the build
+dir. See Project files.
+
+
If a project is open, close it first with File > Close
+Project File. The GUI does not let you analyze files or folders while a
+project is open.
+
+
Analyzing files
+
+
Select Analyze > Files... (Ctrl+F) and select one or more source
+files. The analysis starts at once.
+
+
In the same file dialog you can also select a project of your build
+system. Cppcheck then analyzes the files of that project with its include
+paths and defines, just as if you had imported it in a project file:
+
+
a compile database (compile_commands.json),
+
a Visual Studio solution (*.sln, *.slnx) or
+ project (*.vcxproj). For a solution, a dialog asks which
+ configuration (for example Debug|x64) to analyze,
+
a Borland C++ Builder 6 project (*.bpr).
+
+
+
Analyzing a directory
+
+
Select Analyze > Directory... (Ctrl+D) and select a folder.
+Cppcheck analyzes all source files in the folder and its subfolders. Files
+with these extensions are analyzed: .c, .cpp,
+.cxx, .cc, .c++, .tpp,
+.txx, .ipp and .ixx.
+
+
Header files are not analyzed on their own. The code in a header is
+analyzed when a source file includes it, as long as Cppcheck can find the
+header.
+
+
If the folder contains a Cppcheck project file, the GUI asks if you want to
+open that project instead. If there are several project files, it asks if you
+want to continue without them.
+
+
Which settings are used
+
+
Without a project, the analysis uses:
+
+
the preferences: number of threads,
+ Force checking all #ifdef configurations, Enable inline
+ suppressions, Check for inconclusive errors also,
+
the settings in the Analyze menu: the C and C++ standard, the
+ language (auto-detect, enforce C or enforce C++) and the platform.
+
+
+
The Analyze menu settings are remembered when you close the GUI.
+
+
Language standard
+
+
Analyze > C standard and Analyze > C++ standard select
+the language version. It affects how some code is parsed and which checks
+apply. For example, some functions are deprecated in newer standards, and
+some library functions only exist in newer standards.
+
+
Language
+
+
With Auto-detect language the file extension decides if a file is C
+or C++. Choose Enforce C or Enforce C++ to treat all files as
+one language.
+
+
Platform
+
+
The platform decides the size of the basic types (short,
+int, long, pointers, wchar_t and so on)
+and whether char is signed. Choose the platform the code will run
+on:
+
+
Native: the computer you run Cppcheck on.
+
Unix 32-bit / Unix 64-bit: Linux, BSD, macOS and similar
+ on 32-bit or 64-bit processors.
+
Windows 32-bit ANSI / Windows 32-bit Unicode: 32-bit
+ Windows. In the ANSI variant the Windows "T" macros and types such as
+ TCHAR and _tcscpy mean the char
+ versions; in the Unicode variant they mean the wchar_t
+ versions.
+
Windows 64-bit: 64-bit Windows, with Unicode.
+
+
+
Limitations
+
+
Without a project:
+
+
There are no include paths, so Cppcheck may not find your own headers. You
+ will see information messages about missing includes, and some
+ warnings can be missing because types and macros are unknown.
+
Suppressions made with the context menu can not be saved. The warnings
+ disappear from the list but are reported again in the next analysis. Use
+ inline suppressions in the code instead, or create a project. See
+ Hiding and suppressing warnings.
+
Warnings can not be tagged.
+
There is no build dir. Every analysis analyzes all files, the Since
+ date column is not available, and checks that need whole program
+ analysis, such as unused functions, are not run.
+
Addons and coding standards (Misra, Cert, ...) can not be used.
+
+
+
Analyzing again
-
It is possible to quickly analyze files. Open the Analyze menu and click on
-either Files... or Directory....
+
After you change the code, use Analyze > Reanalyze modified files
+(Ctrl+R) to analyze the files that changed, or Reanalyze all files to
+analyze everything again.
-
It is recommended that you create a project for analysis. A properly configured
-project will give you better analysis.
The difference between hiding and suppressing: a hidden warning
+comes back the next time you analyze the code. A suppressed warning is not
+reported again until you remove the suppression.
+
+
+
Hiding warnings
+
+
Select one or more warnings, right-click and choose Hide N
+result(s). To hide all warnings with the same id as the selected warning,
+choose Hide all with id.
+
+
+
+
Hidden warnings are not deleted. Use View > Show hidden to show
+them again. They are also shown again after the next analysis.
+
+
By default, hidden warnings are not saved when you save the results to a
+file. Enable Save all errors when creating report in the
+preferences to save them too.
+
+
+
Severity buttons and the quick filter
+
+
The buttons in the severity toolbar show or hide all warnings of a
+severity, and the quick filter shows only warnings that contain some text. See
+The main window. These do not change the results;
+they only change what you see.
+
+
+
Suppressing from the context menu
+
+
Select one or more warnings, right-click and choose Suppress selected
+id(s). All warnings with the ids of the selected warnings are removed from
+the list, and a suppression for each id is added to the project file. The
+warnings are not reported in future analyses.
+
+
Note: this suppresses the id in all files of the
+project, not only the selected warning. If you only want to suppress one
+warning, use an inline suppression, or add a suppression
+with a file name or line number in the project dialog.
+
+
This only works when a project is open. Without a project the warnings are
+removed from the list, but nothing is saved. Critical errors, such as syntax
+errors, can not be suppressed this way.
+
+
+
Suppressions in the project dialog
+
+
Open File > Edit Project File... and go to the Warning
+options tab. The Suppressions list shows all suppressions of the
+project, including the ones made with the context menu.
+
+
+
+
Click Add to add a suppression. You must give the Error ID;
+the other fields make the suppression more specific:
+
+
File name: only in files that match. Wildcards * and
+ ? can be used, for example src/legacy/*.
+
Line number: only on this line of the file.
+
Symbol name: only for warnings about this variable or
+ function.
+
+
+
Some examples:
+
+
Error ID
File name
Line
Effect
+
unusedFunction
+
No unused function warnings at all, for example for a library whose
+ functions are called by other programs.
+
*
src/generated/*
+
No warnings in generated code (but the code is still analyzed).
+
nullPointer
src/io.c
+
120
No null pointer warning on line 120 of
+ src/io.c.
+
+
+
Double-click a suppression to edit it, or select it and click
+Remove to remove it. After you click OK, the project is analyzed
+again.
+
+
+
Inline suppressions
+
+
An inline suppression is a comment in the source code, just before the line
+with the warning, or at the end of that line:
You can suppress several ids at once, and add a comment that explains
+why:
+
+
// cppcheck-suppress[nullPointer,uninitvar] get_buffer() never returns NULL
+
+
Inline suppressions are the best way to suppress a single false warning:
+they stay with the code when it is moved, and the next person who reads the
+code sees that the warning was looked at. See the Cppcheck manual for more
+forms, for example suppressing a whole block or file.
+
+
Inline suppressions must be enabled:
+
+
for a project, with Enable inline suppressions on the Warning
+ options tab of the project dialog (enabled by default for new
+ projects),
+
without a project, with Enable inline suppressions in the
+ preferences.
+
+
+
+
Excluding files
+
+
To not analyze some files or folders at all, add them to Exclude source
+files on the Warning options tab of the project dialog. See
+Exclude source files.
Tags let you categorize warnings by hand. You choose the names of the tags
+yourself. Some ideas:
+
+
a review status: todo, checked,
+ fixed later,
+
your opinion of the warning: bug, false
+ positive, not important,
+
who should look at it: frontend, network,
+ team b.
+
-
You choose the names of the categories yourself in the project file
-dialog.
+
Unlike suppressions, tags do not remove warnings. They are a way to keep
+track of what you have looked at and what you have decided about each
+warning.
-
If tag names are configured, then you can tag the warnings by
-right-clicking on them and selecting the proper tag in the
-context menu.
+
Defining tags
-
Tags are saved in the project file and will be permanent.
+
Open File > Edit Project File..., go to the Warning options
+tab and enter the tag names in Warning tags, separated by semicolons:
+
+
todo;false positive;fixed later
+
+
+
+
Tag names can contain letters, digits and spaces. Tags need a project; there
+are no tags when you analyze without a project.
+
+
Tagging warnings
+
+
Select one or more warnings, right-click, open the Tag submenu and
+choose a tag. Choose No tag to remove the tag. The Tag submenu is
+only shown if the project has tags.
+
+
+
+
The tag is shown in the Tags column. Click the column header to sort
+the warnings by tag, so that warnings with the same tag are listed
+together.
+
+
Where tags are stored
+
+
The tags you give to warnings are saved in the project file at once. Each
+tagged warning is identified by a hash of the warning and the code of the
+function it is in, so the tag stays with the warning when code elsewhere in
+the file changes or lines are added above the function. If the function itself
+is changed, the warning gets a new hash and the tag is lost. That is
+intended: when the code has changed, the warning should be looked at
+again.
+
+
Because the tags are in the project file, everyone who uses the project file
+sees the same tags. Tags are also included when you save the results as
+XML.
-
diff --git a/gui/help/tools.html b/gui/help/tools.html
new file mode 100644
index 00000000000..6301283bbd0
--- /dev/null
+++ b/gui/help/tools.html
@@ -0,0 +1,146 @@
+
+
+Scratchpad, library editor and other tools
+
+
+
+
+
Scratchpad, library editor and other tools
+
+
+
Scratchpad
+
+
The scratchpad lets you analyze a piece of code without saving it in a file.
+Use it to try out how Cppcheck handles some code, to reduce a false warning to
+a small example before you report it, or to check a code snippet from a
+review.
+
+
Open it with View > Show Scratchpad....
+
+
+
+
+
Type or paste C or C++ code in the editor.
+
Optionally enter a file name. It is mainly used to decide the language:
+ test.c is analyzed as C and test.cpp as C++. If
+ you leave it empty, the code is analyzed as C++.
+
Click Check.
+
+
+
The results are shown in the results list of the main window, and they
+replace the results that were shown before. The code is analyzed with the
+settings of the open project, or with the settings of the Analyze menu
+if no project is open. The code can not include your own headers, since it is
+not stored in a file.
+
+
The scratchpad keeps its text while the GUI is running, so you can change
+the code and click Check again.
+
+
+
Library editor
+
+
A library configuration file (.cfg) tells Cppcheck how the
+functions of a library behave. Cppcheck comes with configurations for many
+libraries (see Libraries). For
+your own libraries, or libraries that have no configuration, you can write one
+yourself. The library editor helps you with the function part of the
+configuration.
+
+
Open it with View > Library Editor....
+
+
+
+
Working with files
+
+
Click Open to load an existing .cfg file, Save
+to save your changes and Save as to save to a new file. To start a new
+configuration, add functions and click Save as.
+
+
Save your configuration in the folder of your project file. It then
+appears in the Libraries list on the Types and Functions tab of
+the project dialog, where you can select it.
+
+
The editor only shows the functions of a configuration. Other parts of the
+file, such as memory and resource allocation functions, defines and types, are
+kept when you save. If a file contains elements that the editor does not know,
+it can not be opened in the editor; use a text editor for such files.
+
+
Functions
+
+
The list on the left shows the functions of the configuration. Type in
+Filter to show only functions whose names contain the text. Click
+Sort to sort the list by name.
+
+
Click Add to add a function. Enter the function name and the number
+of arguments. If several functions have the same properties, you can enter
+their names separated by commas; they then share one entry.
+
+
Select a function to see and change its properties:
+
+
+
Property
Meaning
+
Comments
Your notes about the function. They are saved as
+ an XML comment before the function in the file.
+
noreturn
True if the function never returns, like
+ exit() or a fatal error handler. Then Cppcheck knows that the
+ code after a call is not reached. False if it always returns.
+ Unknown if it depends.
+
return value must be used
Warn if the return value is ignored,
+ for example for functions that return an error code or a new object.
+
ignore function in leaks checking
The function does not take
+ ownership of a pointer that is passed to it. Without this, Cppcheck may
+ assume that the function frees or stores the memory, and stop tracking a
+ possible leak.
+
Arguments
The properties of each argument. Select an argument
+ and click Edit to change them.
+
+
+
Argument properties
+
+
+
Property
Meaning
+
Not bool
A boolean value (for example the result of a
+ comparison) is wrong for this argument. Typical for pointers and
+ sizes.
+
Not null
A null pointer must not be passed.
+
Not uninit
The value must be initialized. For pointers this
+ means the data it points at is read by the function.
+
String
The argument must be a zero terminated string.
+
Format string
The argument is a printf style format string;
+ the following arguments are checked against it.
+
Min size of buffer
The minimum size of the buffer that is
+ passed. argvalue: the value of another argument (for example the
+ size argument of memset); mul: the product of two
+ arguments; strlen: the length of a string argument.
+
Valid values
The values that are allowed, for example
+ 0: (zero or more), 1:255 or
+ 0,2:5. Cppcheck warns if another value can be passed.
+
+
+
For all features of the configuration format, see the Cppcheck manual and
+the files in the cfg folder of the Cppcheck installation. You can
+edit .cfg files in a text editor too.
+
+
To find which functions in your code are not configured yet, use
+Analyze > Reanalyze and check library; see
+Checking the library
+configuration.
+
+
+
Thread details
+
+
View > Thread Details shows what each analysis thread is doing:
+the file it analyzes, when it started on the file and how far it has come.
+Use it to find files that take a long time to analyze. If one file takes very
+long, you can for example exclude it, reduce the number of configurations with
+undefines, or use a lower check
+level.
missingIncludeSystem: a standard or system header, such as
+ <stdio.h> or <vector>, was not found.
+ This is normal. Cppcheck does not need these headers, because it knows the
+ standard library and the libraries you select in the project. Hide the
+ information severity, or suppress the id
+ missingIncludeSystem in the project.
+
missingInclude: one of your own headers was not found. Fix this:
+ add the folder of the header to the
+ include paths of the project, or import the project from your build
+ system so that Cppcheck gets the same include paths as the compiler.
+
+
+
Use Analyze > Check configuration (defines, includes) to get a
+list of the configuration problems of the project.
+
+
Cppcheck reports a syntax error or an unknown macro
+
+
Cppcheck does not analyze a file it can not parse. A message like There
+is an unknown macro here somewhere. Configuration is required. means that
+the code uses a macro that Cppcheck does not know, in a place where the code
+can not be parsed without knowing what the macro means. For example:
+
+
DECLARE_HANDLER(foo) // what is this?
+{
+ ...
+}
+
+
To fix it, do one of these:
+
+
add the include path of the header that defines the macro,
+
select the library that the macro belongs to (for example qt for
+ Q_OBJECT and signals, or windows for the
+ Windows API macros),
+
give the macro a value in
+ Defines, for example DECLARE_HANDLER(x)=void x(), or in a
+ library configuration file.
+
+
+
If the code is correct and compiles, but Cppcheck still reports a syntax
+error, please report it as a Cppcheck bug.
+
+
The analysis is slow
+
+
+
Use a build dir. Then
+ unchanged files are not analyzed again.
+
Increase the Number of threads in the
+ preferences.
+
Use the Reduced or Normal check level instead of
+ Exhaustive.
+
Make sure that Force checking all #ifdef configurations is off in
+ the preferences.
+
Use undefines to skip
+ configurations you do not need, or import a compile database: then each
+ file is analyzed with one configuration only.
+
Do not add system include folders (such as /usr/include) to
+ the include paths.
Use View > Thread Details to find files that take long; see
+ Thread details.
+
+
+
A warning is wrong (false positive)
+
+
First check the configuration: missing include paths and missing library
+configurations are common causes. For example, if your code calls a function
+that never returns and Cppcheck does not know that, it can report problems in
+code that is never reached. Configure the function as noreturn in a
+library configuration (see Library
+editor).
+
+
If the configuration is right, suppress the warning (see
+Hiding and suppressing warnings) and please
+report the false positive to the Cppcheck project with a small code example.
+Use the scratchpad to make the example as
+small as possible.
+
+
A bug that I know of is not found
+
+
+
Check that the severity of the warning is shown in the severity toolbar,
+ that the quick filter is empty, that no warnings are hidden (View >
+ Show hidden) and that View > Report is Normal.
+
Check that the file was analyzed: look in the Analysis Log.
+
Check that the code was analyzed with the right configuration. If the
+ code is in an #ifdef block, it may only be analyzed when a
+ macro is defined.
+
Select the libraries that the code uses, so that Cppcheck knows how their
+ functions behave.
+
Try the Exhaustive check level and enable inconclusive warnings in
+ the preferences.
+
Bugs that span several files need a build dir (whole program
+ analysis).
+
+
+
Cppcheck can not find all bugs. If you think the bug should be found,
+create a small example in the scratchpad and report it to the Cppcheck
+project.
+
+
+
Checking the library configuration
+
+
Select Analyze > Reanalyze and check library. The project is
+analyzed again, and Cppcheck reports information messages for functions
+it has no configuration for, for example:
+
+
checkLibraryNoReturn: Cppcheck does not know if the function
+ can return. Configure noreturn for it.
+
checkLibraryFunction: the function is not configured at all.
+ If it is a library function, configure it so that Cppcheck can check how it
+ is used.
+
+
+
Write the missing configuration in a .cfg file with the
+library editor or a text editor, put it
+in the folder of the project file and select it on the Types and
+Functions tab of the project.
+
+
Addons do not work
+
+
+
Addons are Python scripts. Install Python, and if it is not in the PATH,
+ set the Python binary in the
+ preferences.
+
The project needs a build
+ dir. Without it, the analysis is stopped with the message To check
+ the project using addons, you need a build directory.
+
If an addon is shown as (Not found) in the project dialog, the
+ Cppcheck installation is incomplete.
+
+
+
Clang-Tidy is "not found"
+
+
Install Clang-Tidy (it is part of LLVM/Clang). If it is not in the PATH, set
+the Clang path in the preferences to the
+folder where clang-tidy is.
+
+
Double-clicking a warning does nothing
+
+
No editor is configured, or no default editor is selected. Add your editor
+on the Applications tab of the
+preferences and click Set as default. If the editor does not start,
+check the executable path and the parameters.
+
+
The results are old
+
+
When you open a project with a build dir, the results of the last analysis
+are shown, not a new analysis. Click Reanalyze all files to analyze the
+current code.
+
+
"Failed to load std.cfg"
+
+
The GUI can not find its data files. The Cppcheck installation is broken;
+reinstall Cppcheck. If you built Cppcheck yourself, make sure the
+cfg folder is installed next to the program, or configure the data
+folder with the --data-dircommand line
+option.
+
+
+
diff --git a/gui/help/walkthrough.html b/gui/help/walkthrough.html
index 9f27ce05f6c..dbaff99a073 100644
--- a/gui/help/walkthrough.html
+++ b/gui/help/walkthrough.html
@@ -1,69 +1,156 @@
-Walk through
+Getting started
+
-
Quick walk through
+
Getting started
-This is a quick and short walk through to get you started.
+
This page walks you through a first analysis: you create a project file,
+run the analysis and look at the results. It takes a few minutes. The other
+pages in this help explain each step in more detail.
-
Step 1: Create a project.
+
Step 1: Create a project file
-
Create a new project:
+
Select File > New Project File... (Ctrl+Shift+N). A file dialog
+asks where to save the project file. Save it in the root folder of your code,
+for example next to your CMakeLists.txt or Visual Studio
+solution. Paths in the project file are stored relative to the project file,
+so the project keeps working if you move the whole folder or check it into
+version control.
-
+
When you have chosen a file name, the Project file dialog opens. A
+new project automatically gets a build dir called
+<name>-cppcheck-build-dir. Cppcheck stores its work files
+there; it makes repeated analysis much faster and is required for some
+features. See Cppcheck build dir.
-
In the Paths and Defines tab, it is recommended that you import your
-project file at the top.
+
Step 2: Tell Cppcheck where the code is
-
+
Open the Paths and Defines tab. You have two choices.
-
In the Types and Functions tab, try to activate all 3rd party
-libraries you use (windows, posix, ...).
+
Choice A: Import a project (recommended)
-
+
If you build your code with Visual Studio, CMake, Meson, Bear or any other
+tool that can produce a compile database, import it. Click Browse...
+next to Import Project and select one of:
+
+
a Visual Studio solution (*.sln, *.slnx) or
+ project (*.vcxproj),
+
a compile database (compile_commands.json). CMake creates it
+ if you configure with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON,
+
a Borland C++ Builder 6 project (*.bpr).
+
-
In the Analysis tab, leave the default settings to start with.
+
Cppcheck then reads the list of source files, the include paths, the
+preprocessor defines and the language standard from the imported file. The
+Paths, Include Paths and Undefines fields are disabled
+because the values come from the imported project.
-
In the Warnings options tab, leave the default settings to start
-with.
+
-
In the Addons tab, leave the default settings to start with.
+
Choice B: Enter the paths yourself
+
If you can not import a project, enter the information manually:
+
+
Paths: click Add... and select the folders (or files) to
+ analyze. Cppcheck analyzes all C/C++ source files in these folders and
+ their subfolders.
+
Include Paths: add the folders where your own header files are.
+ Cppcheck needs your headers to understand types, macros and functions.
+ You do not need to add the standard library or system headers; Cppcheck
+ has built-in knowledge about them.
+
Defines: optionally enter macros, for example
+ DEBUG;VERSION=3.
+
-
Step 2: Analyze code.
+
-
When the project file has been created, the analysis will start
-automatically.
+
Step 3: Select libraries
-
While analysis is performed in the background, you can investigate the
-results.
+
Open the Types and Functions tab. In the Libraries list,
+check the libraries your code uses, for example posix, windows,
+qt, boost, gtk or sdl. A library configuration tells
+Cppcheck how the functions and types in that library behave: which functions
+allocate memory, which arguments must not be null, which functions never
+return, and so on. This gives you more and better warnings.
-
+
Set the Platform to the target of your code if it differs from the
+computer you run Cppcheck on. This matters for portability and overflow
+checks, since the sizes of int, long and pointers
+differ between platforms.
+
-
Step 3: Investigate warnings.
+
Step 4: Leave the rest as it is
-
In the toolbar you choose what types of warnings you want to see
-(error/warning/style/performance/portability/information).
+
The Analysis, Warning options and Addons tabs have good
+default values. You can come back later and change them; see
+Project files for a description of every
+option.
-
+
Click OK. The project file is saved. If the build dir does not exist
+yet, the GUI asks if it should create it: answer Yes.
+
Step 5: Watch the analysis
-
All warnings are shown in a list. If you select a warning in the list, then
-details about that warning is shown.
+
The analysis starts immediately. A progress bar above the results shows
+how many files have been analyzed. You do not have to wait: warnings appear
+in the list as soon as they are found, and you can start looking at them.
+To stop the analysis, click the Stop button in the toolbar or press
+Esc.
-
If you right click on warning(s) then you get a context menu.
+
Step 6: Look at the results
-
+
The results list groups the warnings by file. Click the arrow next to a
+file name, or use View > Expand all, to see the warnings. Click a
+warning to show its details and the source code in the lower part of the
+window. The line with the problem is highlighted.
-
The difference of "Hide" and "Suppress" is that
-suppressions are saved in the project file. The suppressed warnings will not be
-shown again unless you remove the suppression. When you hide a warning then
-they will be temporarily hidden; the next time you analyze your code these
-warnings will be shown again.
+
+
+
Some warnings have an arrow in front of them. Expand them to see
+notes: these show the path through the code that leads to the
+problem, for example where a variable got the value that later causes the
+bug. Click a note to jump to that line.
+
+
Use the colored buttons in the toolbar to show or hide warnings by
+severity. Start with errors and warnings; they are the most likely to be
+real bugs. See Severities.
+
+
+
+
Right-click a warning to get more options: open the file in your editor,
+copy the warning, hide it, suppress it or tag it.
Fix the bugs in your editor. Then click Reanalyze all files in the
+toolbar. Because the project has a build dir, Cppcheck only does the full
+analysis again for files that changed (or that include a changed header). For
+the other files the results are taken from the build dir, so this is
+fast.
The project files you used recently are listed in the File menu.
+When you open a project that has a build dir, the GUI first shows the results
+from the last analysis. Click Reanalyze all files in the toolbar to
+analyze the code again. Thanks to the build dir, files that did not change
+are not analyzed again; their old results are reused.
-
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index 3ed37eb7b25..5ddb790b77b 100644
--- a/gui/mainwindow.cpp
+++ b/gui/mainwindow.cpp
@@ -59,6 +59,7 @@
#include
#include
+#include
#include
#include
#include
@@ -96,6 +97,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -118,6 +120,18 @@ static QString fromNativePath(const QString& p) {
#endif
}
+// The files selected in the results view are not necessarily spelled the same way as the
+// project or files.txt spells them, compare them as absolute paths
+static QString comparablePath(const QString& path) {
+ return QDir::cleanPath(QFileInfo(path).absoluteFilePath());
+}
+
+static QSet comparablePaths(const QStringList& paths) {
+ QSet ret;
+ std::transform(paths.cbegin(), paths.cend(), std::inserter(ret, ret.end()), comparablePath);
+ return ret;
+}
+
template>
static T toStdStringList(const QStringList& stringList) {
T ret;
@@ -568,21 +582,31 @@ void MainWindow::saveSettings() const
mUI->mResults->saveSettings(mSettings);
}
-void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& recheckFiles)
+void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck)
{
Settings checkSettings;
auto supprs = std::make_shared();
if (!getCppcheckSettings(checkSettings, *supprs))
return;
- // filter requested files
- if (!recheckFiles.isEmpty()) {
+ // Filter the selected files
+ if (recheck == Recheck::Selected) {
+ const QSet selected = comparablePaths(selectedFiles);
p.fileSettings.remove_if([&](const FileSettings& fs) {
- return !recheckFiles.contains(QString::fromStdString(fs.filename()));
+ return !selected.contains(comparablePath(QString::fromStdString(fs.filename())));
});
+ if (p.fileSettings.empty()) {
+ QMessageBox msg(QMessageBox::Information,
+ "Cppcheck",
+ tr("None of the selected files is part of the project, nothing to analyze."),
+ QMessageBox::Ok,
+ this);
+ msg.exec();
+ return;
+ }
}
- clearResults();
+ clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck);
mIsLogfileLoaded = false;
if (mProjectFile) {
@@ -647,7 +671,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo
mUI->mResults->setCheckSettings(checkSettings);
}
-void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig)
+void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck)
{
if (files.isEmpty())
return;
@@ -657,7 +681,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c
if (!getCppcheckSettings(checkSettings, *supprs))
return;
- clearResults();
+ clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck);
mIsLogfileLoaded = false;
FileList pathList;
@@ -669,7 +693,6 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c
}
QStringList fileNames = pathList.getFileList();
- mUI->mResults->clear(true);
mUI->mResults->setResultsSource(ResultsTree::ResultsSource::Analysis);
mThread->clearFiles();
@@ -1379,11 +1402,6 @@ void MainWindow::reAnalyzeSelected(const QStringList& files)
return;
if (mProjectFile) {
- // Clear details, statistics and progress
- mUI->mResults->clear(false);
- for (int i = 0; i < files.size(); ++i)
- mUI->mResults->clearRecheckFile(files[i]);
-
analyzeProject(mProjectFile, files);
return;
}
@@ -1393,10 +1411,7 @@ void MainWindow::reAnalyzeSelected(const QStringList& files)
if (!getCppcheckSettings(checkSettings, *supprs))
return;
- // Clear details, statistics and progress
- mUI->mResults->clear(false);
- for (int i = 0; i < files.size(); ++i)
- mUI->mResults->clearRecheckFile(files[i]);
+ clearResultsForAnalysis(checkSettings.buildDir, files, Recheck::Selected);
mCurrentDirectory = mUI->mResults->getCheckDirectory();
FileList pathList;
@@ -1451,7 +1466,7 @@ void MainWindow::reAnalyze(bool all)
void MainWindow::clearResults()
{
if (mProjectFile && !mProjectFile->getBuildDir().isEmpty()) {
- QDir dir(QFileInfo(mProjectFile->getFilename()).absolutePath() + '/' + mProjectFile->getBuildDir());
+ QDir dir(QDir(QFileInfo(mProjectFile->getFilename()).absolutePath()).filePath(mProjectFile->getBuildDir()));
for (const QString& f: dir.entryList(QDir::Files)) {
if (!f.endsWith("files.txt")) {
static const QRegularExpression rx("^.*.s[0-9]+$");
@@ -1465,6 +1480,42 @@ void MainWindow::clearResults()
enableResultsButtons();
}
+void MainWindow::clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, const Recheck recheck)
+{
+ switch (recheck) {
+ case Recheck::None:
+ clearResults();
+ break;
+ case Recheck::Selected:
+ // Only the selected files are analyzed, the results of the other files are kept
+ mUI->mResults->clear(false);
+ for (const QString& f : selectedFiles)
+ mUI->mResults->clearRecheckFile(f);
+ enableResultsButtons();
+ break;
+ case Recheck::Cached: {
+ // Every file reports its results again, either analyzed or replayed from the analyzer
+ // information. Remove the analyzer information of the selected files so that they really
+ // are analyzed again, also by the clang tools that keep their results next to it.
+ const QSet selected = comparablePaths(selectedFiles);
+ QDir dir(QString::fromStdString(buildDir));
+ std::ifstream filesTxt(buildDir + "/files.txt");
+ std::string line;
+ while (std::getline(filesTxt, line)) {
+ AnalyzerInformation::Info info;
+ if (!info.parse(line) || !selected.contains(comparablePath(QString::fromStdString(info.sourceFile))))
+ continue;
+ const QString afile = QString::fromStdString(info.afile);
+ for (const QString& f : dir.entryList({afile, afile + ".*"}, QDir::Files))
+ dir.remove(f);
+ }
+ mUI->mResults->clear(true);
+ enableResultsButtons();
+ break;
+ }
+ }
+}
+
void MainWindow::openResults()
{
if (mUI->mResults->hasResults()) {
@@ -1863,10 +1914,17 @@ bool MainWindow::loadLastResults()
return true;
}
-void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, const bool checkLib, const bool checkConfig)
+void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, const bool checkLib, const bool checkConfig)
{
Settings::terminate(false);
+ // With a build directory the whole project is analyzed: the analyzer information cache skips
+ // the unchanged files, and files.txt stays complete so whole program analysis sees every file.
+ // Without a build directory there is no cache, so only the selected files are analyzed.
+ Recheck recheck = Recheck::None;
+ if (!selectedFiles.isEmpty())
+ recheck = projectFile->getBuildDir().isEmpty() ? Recheck::Selected : Recheck::Cached;
+
QFileInfo inf(projectFile->getFilename());
const QString& rootpath = projectFile->getRootPath();
@@ -1968,11 +2026,11 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis
msg.exec();
return;
}
- doAnalyzeProject(p, checkLib, checkConfig, recheckFiles); // TODO: avoid copy
+ doAnalyzeProject(p, checkLib, checkConfig, selectedFiles, recheck); // TODO: avoid copy
return;
}
- QStringList paths = recheckFiles.isEmpty() ? projectFile->getCheckPaths() : recheckFiles;
+ QStringList paths = recheck == Recheck::Selected ? selectedFiles : projectFile->getCheckPaths();
// If paths not given then check the root path (which may be the project
// file's location, see above). This is to keep the compatibility with
@@ -1981,7 +2039,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis
if (paths.isEmpty()) {
paths << mCurrentDirectory;
}
- doAnalyzeFiles(paths, checkLib, checkConfig);
+ doAnalyzeFiles(paths, checkLib, checkConfig, selectedFiles, recheck);
}
void MainWindow::newProjectFile()
diff --git a/gui/mainwindow.h b/gui/mainwindow.h
index c654a2c94f0..a92476a8c20 100644
--- a/gui/mainwindow.h
+++ b/gui/mainwindow.h
@@ -24,6 +24,7 @@
#include
#include
+#include
#include
#include
@@ -249,6 +250,19 @@ private slots:
void changeReportType();
private:
+ /** @brief How an analysis treats the files selected for a recheck */
+ enum class Recheck : std::uint8_t {
+ /** Not a recheck, everything is analyzed from scratch */
+ None,
+ /** Only the selected files are analyzed, the results of the other files are kept */
+ Selected,
+ /**
+ * Everything is analyzed. The analyzer information in the build directory is kept,
+ * except for the selected files, so the unchanged files are not analyzed again.
+ */
+ Cached
+ };
+
bool isCppcheckPremium() const;
/** Get filename for last results */
@@ -266,11 +280,11 @@ private slots:
/**
* @brief Analyze the project.
* @param projectFile Pointer to the project to analyze.
- * @param recheckFiles files to recheck, empty => check all files
+ * @param selectedFiles files to recheck, empty => check all files
* @param checkLib Flag to indicate if the library should be checked.
* @param checkConfig Flag to indicate if the configuration should be checked.
*/
- void analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, bool checkLib = false, bool checkConfig = false);
+ void analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, bool checkLib = false, bool checkConfig = false);
/**
* @brief Set current language
@@ -308,8 +322,10 @@ private slots:
* @param p imported project
* @param checkLib Flag to indicate if library should be checked
* @param checkConfig Flag to indicate if the configuration should be checked.
+ * @param selectedFiles Files selected for a recheck
+ * @param recheck How the selected files are rechecked
*/
- void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList& recheckFiles = QStringList());
+ void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None);
/**
* @brief Analyze all files specified in parameter files
@@ -317,8 +333,18 @@ private slots:
* @param files List of files and/or directories to analyze
* @param checkLib Flag to indicate if library should be checked
* @param checkConfig Flag to indicate if the configuration should be checked.
+ * @param selectedFiles Files selected for a recheck
+ * @param recheck How the selected files are rechecked
+ */
+ void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None);
+
+ /**
+ * @brief Clear the results that a new analysis replaces
+ * @param buildDir Build directory of the analysis
+ * @param selectedFiles Files selected for a recheck
+ * @param recheck How the selected files are rechecked
*/
- void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false);
+ void clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, Recheck recheck);
/**
* @brief Get our default cppcheck settings and read project file.
diff --git a/gui/manualtest/docs/handbook.html b/gui/manualtest/docs/handbook.html
new file mode 100644
index 00000000000..1da9f571224
--- /dev/null
+++ b/gui/manualtest/docs/handbook.html
@@ -0,0 +1,453 @@
+
+
+
+
+
+gui-test Handbook
+
+
+
+
+
+
+
gui-test
+
gui-test Handbook
+
The manual tests for cppcheck-gui are written in Gherkin, the plain-language Given/When/Then format. gui-test turns them 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-24
+
+
Overview
+
Working with gui-test has three steps:
+
+
Write the test in plain words: what the starting point is, what you do and what you expect to see.
+
Generate the script, once. Claude does the test in the application and writes down what it did as a script.
+
Run the tests as often as you like. The scripts are replayed without AI, and you get a report with the results and screenshots.
+
+
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.
+
+
+
+From a test in words to a report: gen.py once, run.py every run.
+
+
+
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.
--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.
+
+
Some packages must be installed first. On Ubuntu (checked on 26.04):
The tesseract packages are only needed for ocr checks. To generate scripts the claude command line tool must be installed too.
+
+
The web interface
+
+
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
+
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
+
+
Keyword
Meaning 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)
+
Given
The starting point: the state that the application is in before the test does anything
+
When
An action of the user: a click, a menu entry, typing, opening a file
+
Then
The 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
+
@tag
Tags on the line above a scenario, see below
+
# text
A 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.
+
+
Tag
Meaning
+
+
@id:t1a2b3c4d
Identifies 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:14893
The ticket of the test, shown as #14893
+
@project:NAME
A test project that the test uses, one tag per project
+
@edition:opensource
An edition that the test is for, one tag per edition. Without the tag the test is for all editions
+
@disabled
The 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
+
+ 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
+
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 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.
+
+
Field
What to write
+
+
Name
Unique within the file. Shown in reports and the history, so a rename starts a new history line
+
Ticket
Optional, e.g. a Trac ticket number
+
enabled
Untick to leave the test out of every run without deleting it
+
Editions
Tick 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 steps
One 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
+
TODO
Why 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 projects
Tick the projects whose files the test uses, see below
+
Script
The script of the test, with the buttons Generate script, Run test and, when it is outdated, Approve the script. See Scripts
+
Examples
A Gherkin table; every row is a separate run and the test becomes a Scenario Outline
+
+
+
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.
+
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 (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 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.
+
+
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"}
+]
+
+
Step
What it does
+
+
do: click, rightclick, doubleclick, focus, hover
Act on an element; "mouse": true clicks with the mouse
+
do: settext
Replace the text of an edit field with value
+
do: type, key
Keyboard input to the focused element; keys uses xdotool names like ctrl+o, Return
+
do: wait
Wait for an element to appear, or with "gone": true to disappear
+
do: restart
Quit and start the application again; settings are kept
+
do: sleep, write, copy
Pause, write a file in {work}, or copy a file in a test project
+
expect: visible, gone
An element is shown, or is not
+
expect: state
An element is or is not checked, enabled, focused, selected, expanded...
+
expect: value
The value or text of a field equals something
+
expect: text, ocr
Text is (not) on the screen; ocr reads pixels with tesseract, for context menus AT-SPI can't see
+
expect: file
A file contains, does not contain or matches a regex
+
expect: alive, exited
The application is still running, or has quit by itself with status 0
+
expect: capture
Search 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}, {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, 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:
+
+
Generate script again.
+
Approve the script when the change does not affect it, e.g. a typo fix in a step.
+
Edit the script by hand in the editor; it has to be valid JSON before you can save.
+
+
+
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 the rows of its Examples.
+
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.
APP is this edition; only the tests for it run. Without it all tests run and there is no history per edition
+
--test TEXT
Only tests whose name contains TEXT (case-insensitive)
+
--id ID
Only the test with this id, all the rows of its Examples
+
--src DIR
Git checkout for the SHA in the report; default the directory of APP
+
--out DIR
Where 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.feature; it takes --edition, --test, --id, --force (also tests that have a script), --model and --attempts.
+
+
What happens in a run
+
+
The tests to run are selected: enabled, for this edition, not filtered out. Tests without a script, or with an outdated one, are SKIP.
+
For each test, the test projects are copied and the application is started on a new virtual screen with an empty HOME.
+
The script is replayed step by step. After every step that acts on the GUI (click, type, key, settext, restart...) a screenshot is saved.
+
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.
+
After the last test, results.json and report.html are written in the run directory.
+
+
+
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 from three runs of two test files.
+
+
+
Part
What it tells you
+
+
Header
FAILED 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 boxes
Passed, failed (how many crashed the app), skipped, duration, and with history the change against the previous run
+
Results per test file
A 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 runs
Pass, 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 run
NEW failures, tests that are fixed, and tests that are STILL FAILING
+
Status history
A 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 tests
A card per test: the reason, the step strip, the screenshots after the last 3 steps and the final screen with a red frame
+
All tests
Every 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 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.
+
+
File
Content
+
+
report.html
The report
+
results.json
The run details and every result. The history is read from these files
+
NAME.txt
The step log: each step, ok or FAIL, its time and its screenshot
+
NAME.png
The screen at the end of the test, or when it failed
+
NAME.stepNN.png
The screen after step NN, for steps that act on the GUI
+
NAME.app.log
What the application printed
+
+
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.
+
+
What you see
What 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 reason
Claude 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 CRASH
The 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 history
The 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 found
Some 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 out
web.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.
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
+
+
Command
What it does
+
+
start APP [ARGS]
Start Xvfb, D-Bus, AT-SPI and the application. --home DIR (default an empty directory), --size 1280x900
+
stop, restart
Stop everything; quit and start the application again with its settings kept
+
alive, log
Exit 0 if the application runs; print what it wrote to stdout and stderr
+
windows, texts
The top level windows and dialogs; every visible text
+
tree
The accessibility tree. --showing only visible elements, --depth N, --path P one subtree
+
click, rightclick, doubleclick, focus, hover
Act on an element found with --name, --role, --path, --nth, --exact. --mouse for a real mouse click
+
settext VALUE, type VALUE, key KEYS
Set a field's text; type into the focused element; press keys (xdotool names)
+
wait
Wait for an element, --timeout 10, --gone to wait until it disappears
+
info, suggest
Role, name, text, value and states of one element; the visible elements most like --name/--role
+
ocr, screenshot FILE
Text 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 00000000000..2a24756da06
Binary files /dev/null and b/gui/manualtest/docs/report.png differ
diff --git a/gui/manualtest/gen.py b/gui/manualtest/gen.py
new file mode 100755
index 00000000000..0a3360065dd
--- /dev/null
+++ b/gui/manualtest/gen.py
@@ -0,0 +1,234 @@
+#!/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.feature [--edition opensource|premium] [--test SUBSTRING] [--force] [--model M]
+
+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.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.
+"""
+
+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}
+
+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 (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, 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
+- {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, 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 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 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'
+ 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, 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]
+ 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(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))
+ 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, 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, doc.get('background'), 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..0b28cd7031d
--- /dev/null
+++ b/gui/manualtest/guitest.py
@@ -0,0 +1,715 @@
+#!/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)
+ # Cppcheck Premium needs its license file in HOME
+ lic = os.path.join('.cppcheckpremium', 'cppcheck-premium.lic')
+ src_lic = os.path.join(os.path.expanduser('~'), lic)
+ dst_lic = os.path.join(home, lic)
+ if os.path.isfile(src_lic) and os.path.realpath(src_lic) != os.path.realpath(dst_lic):
+ os.makedirs(os.path.dirname(dst_lic), exist_ok=True)
+ shutil.copy2(src_lic, dst_lic)
+ 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 (''
+ % (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 (''
+ % (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 \
+ '
'
+ % (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('
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 '
' % (
+ ' 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)
' % ''.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..1d7668cda27
--- /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.feature... [--edition opensource|premium] [--test SUBSTRING] [--id ID] [--src DIR] [--out DIR]
+
+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
+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 .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 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')
+ 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, file=test['file'], 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..a915e7960fa
--- /dev/null
+++ b/gui/manualtest/testfile.py
@@ -0,0 +1,743 @@
+#!/usr/bin/env python3
+"""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 scripts are kept in
+FILE.scripts.json, {"ID": {"script": [...], "script_for": "..."}}, next to the feature file.
+
+@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 steps and scripts is the copy of NAME,
+and {project} when the test has exactly one project.
+
+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.
+
+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|IN.json OUT.feature import a markdown or an older JSON 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')
+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. {column} values of the Examples row 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
+
+
+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': []}
+ 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))
+ test['steps'] = legacy_steps(test['steps'], test.pop('expected'))
+ if not test['todo']:
+ del test['todo']
+ 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']
+ 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
+ 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 '\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)
+ 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 -)')
+ 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')
+ 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
+ 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()
+ 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 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):
+ """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 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('Examples row for this run: ' + ', '.join('%s=%s' % kv for kv in combo.items()))
+ combo = dict(combo, edition=edition) if edition else 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_step(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, 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']}
+ 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, 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, 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)
+ 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)
+ 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, doc.get('background'))
+ else:
+ t['script_for'] = p.get('script_for') or definition_hash(p, root, old.get('background'))
+ 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 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):
+ 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, 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 write_atomic(path, text):
+ tmp = path + '.tmp'
+ with open(tmp, 'w', encoding='utf-8') as f:
+ 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' or not sys.argv[3].endswith(EXT):
+ sys.exit(__doc__)
+ 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.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.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..70d0a82fa47
--- /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 @disabled
+ 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.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.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..a4c2324551e
--- /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 @edition:opensource
+ 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.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/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 00000000000..c91312c3af0
Binary files /dev/null and b/gui/manualtest/tests/projects/syntax-error/binary.c differ
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.feature b/gui/manualtest/tests/result_list.feature
new file mode 100644
index 00000000000..f370d296fb0
--- /dev/null
+++ b/gui/manualtest/tests/result_list.feature
@@ -0,0 +1,91 @@
+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 all with id
+ Then the a.c results are hidden
+ When 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.scripts.json b/gui/manualtest/tests/result_list.scripts.json
new file mode 100644
index 00000000000..739dd0bd32e
--- /dev/null
+++ b/gui/manualtest/tests/result_list.scripts.json
@@ -0,0 +1,1127 @@
+{
+ "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": "Given the project file is opened from the File menu: open File menu"
+ },
+ {
+ "do": "click",
+ "name": "Open Project File...",
+ "role": "menu item",
+ "label": "Given the project file is opened from the File menu: Open Project File"
+ },
+ {
+ "do": "wait",
+ "name": "Select Project File",
+ "role": "dialog",
+ "label": "Project file dialog is shown"
+ },
+ {
+ "do": "settext",
+ "name": "File name:",
+ "role": "text",
+ "value": "{project:basic-project}/basic.cppcheck",
+ "label": "Enter the project file basic.cppcheck"
+ },
+ {
+ "do": "key",
+ "keys": [
+ "Return"
+ ],
+ "label": "Open the project file"
+ },
+ {
+ "expect": "visible",
+ "name": "Project: basic.cppcheck",
+ "role": "frame",
+ "timeout": 10,
+ "label": "The project is loaded"
+ },
+ {
+ "expect": "visible",
+ "name": "src/a.c",
+ "role": "table cell",
+ "timeout": 30,
+ "label": "And the analysis is done: a.c is in the results"
+ },
+ {
+ "expect": "state",
+ "name": "Stop analysis",
+ "role": "button",
+ "not_state": "enabled",
+ "timeout": 30,
+ "label": "And the analysis is done"
+ },
+ {
+ "do": "focus",
+ "name": "src/a.c",
+ "role": "table cell",
+ "label": "When I expand a.c in the results: select a.c"
+ },
+ {
+ "do": "key",
+ "keys": [
+ "Right"
+ ],
+ "label": "When I expand a.c in the results"
+ },
+ {
+ "expect": "visible",
+ "name": "Null pointer dereference: p",
+ "role": "table cell",
+ "label": "The first nullPointer error of a.c is shown"
+ },
+ {
+ "expect": "visible",
+ "name": "Null pointer dereference: q",
+ "role": "table cell",
+ "label": "The second nullPointer error of a.c is shown"
+ },
+ {
+ "do": "rightclick",
+ "name": "Null pointer dereference: p",
+ "role": "table cell",
+ "label": "And I right click on the first nullPointer error"
+ },
+ {
+ "expect": "ocr",
+ "contains": "Hide all with id",
+ "label": "The context menu has Hide all with id"
+ },
+ {
+ "do": "key",
+ "keys": [
+ "Up",
+ "Up",
+ "Up"
+ ],
+ "label": "Move to Hide all with id in the context menu"
+ },
+ {
+ "do": "key",
+ "keys": [
+ "Return"
+ ],
+ "label": "And click Hide all with id"
+ },
+ {
+ "expect": "gone",
+ "name": "Null pointer dereference: p",
+ "label": "Then the a.c results are hidden: first nullPointer error"
+ },
+ {
+ "expect": "gone",
+ "name": "Null pointer dereference: q",
+ "label": "Then the a.c results are hidden: second nullPointer error"
+ },
+ {
+ "expect": "visible",
+ "name": "Variable 'p' can be declared as pointer to const",
+ "role": "table cell",
+ "label": "Other a.c results are still shown"
+ },
+ {
+ "do": "click",
+ "name": "View",
+ "role": "menu item",
+ "label": "When I open View menu"
+ },
+ {
+ "do": "click",
+ "name": "Show hidden",
+ "role": "menu item",
+ "label": "When I open View > Show Hidden"
+ },
+ {
+ "expect": "visible",
+ "name": "Null pointer dereference: p",
+ "role": "table cell",
+ "label": "Then the error that was hidden is shown again"
+ },
+ {
+ "expect": "visible",
+ "name": "Null pointer dereference: q",
+ "role": "table cell",
+ "label": "Then the other hidden nullPointer error is shown again"
+ }
+ ],
+ "script_for": "0b1c29212e8d"
+ },
+ "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..2a7b48c5285
--- /dev/null
+++ b/gui/manualtest/tests/xml_results.feature
@@ -0,0 +1,45 @@
+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
+ Then the button Reanalyze all files is disabled
+ And if I rightclick on a.c in the results I get a popup
+ And there is a option "Recheck 1 file(s)" in the popup that is disabled
+ 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.scripts.json b/gui/manualtest/tests/xml_results.scripts.json
new file mode 100644
index 00000000000..95fa6cebb97
--- /dev/null
+++ b/gui/manualtest/tests/xml_results.scripts.json
@@ -0,0 +1,466 @@
+{
+ "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": [
+ {
+ "expect": "visible",
+ "name": "Cppcheck",
+ "role": "frame",
+ "label": "Given the application is started with no project loaded"
+ },
+ {
+ "do": "click",
+ "name": "File",
+ "role": "menu item",
+ "label": "Given the XML results are opened with File > Open XML: open the File menu"
+ },
+ {
+ "do": "click",
+ "name": "Open XML...",
+ "role": "menu item",
+ "label": "Given the XML results are opened with File > Open XML: choose Open XML"
+ },
+ {
+ "expect": "visible",
+ "name": "Open the report file",
+ "role": "dialog",
+ "label": "The Open XML file dialog is shown"
+ },
+ {
+ "do": "settext",
+ "name": "File name:",
+ "role": "text",
+ "value": "{project:xml-results}/results.xml",
+ "label": "Enter the XML results file name"
+ },
+ {
+ "do": "key",
+ "keys": [
+ "Return"
+ ],
+ "label": "Open the XML results"
+ },
+ {
+ "do": "wait",
+ "name": "Open the report file",
+ "gone": true,
+ "timeout": 10,
+ "label": "Wait until the file dialog is closed"
+ },
+ {
+ "expect": "visible",
+ "name": "results.xml",
+ "role": "frame",
+ "timeout": 10,
+ "label": "The XML results are loaded"
+ },
+ {
+ "expect": "visible",
+ "name": "a.c",
+ "role": "table cell",
+ "timeout": 10,
+ "label": "a.c is shown in the results"
+ },
+ {
+ "expect": "state",
+ "name": "Reanalyze all files",
+ "role": "button",
+ "not_state": "enabled",
+ "label": "Then the button Reanalyze all files is disabled"
+ },
+ {
+ "do": "rightclick",
+ "name": "a.c",
+ "role": "table cell",
+ "label": "And if I rightclick on a.c in the results"
+ },
+ {
+ "do": "sleep",
+ "seconds": 1
+ },
+ {
+ "expect": "ocr",
+ "contains": "Copy",
+ "label": "I get a popup"
+ },
+ {
+ "expect": "ocr",
+ "not_contains": "Recheck",
+ "label": "There is an option Recheck 1 file(s) in the popup that is disabled (greyed out, not read by OCR)"
+ },
+ {
+ "do": "key",
+ "keys": [
+ "Escape"
+ ],
+ "label": "Close the popup"
+ },
+ {
+ "do": "click",
+ "name": "File",
+ "role": "menu item",
+ "label": "When I open the project file: open the File menu"
+ },
+ {
+ "do": "click",
+ "name": "Open Project File...",
+ "role": "menu item",
+ "label": "When I open the project file: choose Open Project File"
+ },
+ {
+ "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 project file name"
+ },
+ {
+ "do": "key",
+ "keys": [
+ "Return"
+ ],
+ "label": "Open the project file"
+ },
+ {
+ "do": "wait",
+ "name": "Select Project File",
+ "gone": true,
+ "timeout": 10,
+ "label": "Wait until the file dialog is closed"
+ },
+ {
+ "expect": "visible",
+ "name": "Project: basic.cppcheck",
+ "role": "frame",
+ "timeout": 10,
+ "label": "The project is loaded"
+ },
+ {
+ "expect": "state",
+ "name": "Stop analysis",
+ "role": "button",
+ "not_state": "enabled",
+ "timeout": 30,
+ "label": "And I wait until the analysis is done"
+ },
+ {
+ "expect": "state",
+ "name": "Reanalyze all files",
+ "role": "button",
+ "state": "enabled",
+ "timeout": 30,
+ "label": "Then the button Reanalyze all files is enabled"
+ },
+ {
+ "do": "click",
+ "name": "Reanalyze all files",
+ "role": "button",
+ "label": "And can be clicked"
+ },
+ {
+ "expect": "alive",
+ "label": "The application still runs after clicking Reanalyze all files"
+ },
+ {
+ "expect": "state",
+ "name": "Reanalyze all files",
+ "role": "button",
+ "state": "enabled",
+ "timeout": 30,
+ "label": "Reanalyze all files is enabled again after the reanalysis"
+ }
+ ],
+ "script_for": "299ae476b3db"
+ }
+}
diff --git a/gui/manualtest/web.py b/gui/manualtest/web.py
new file mode 100644
index 00000000000..e538b00bdb8
--- /dev/null
+++ b/gui/manualtest/web.py
@@ -0,0 +1,464 @@
+#!/usr/bin/env python3
+"""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/*.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.
+"""
+
+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.-]+\.feature$')
+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': '', 'testfile': '', '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='', testfile='', 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'], testfile=e.get('file', ''), 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')},
+ file=e.get('file', ''),
+ 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 .feature)'})
+ 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
+ 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, 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 Gherkin."""
+ out = {}
+ for name in os.listdir(self.directory):
+ if not NAME_RE.match(name):
+ continue
+ try:
+ 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'])) + 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}
+ 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:
+ 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 Gherkin (fix it in a text editor): %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/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)
+ 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'})
+
+
+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..7cccc8e9b23
--- /dev/null
+++ b/gui/manualtest/web/index.html
@@ -0,0 +1,677 @@
+
+
+
+
+
+GUI tester
+
+
+
+
+