I am using QTestLib Library and QTest for running my unit tests. I am working on windows 7 and using Qt 4.8 When I run my test using:
int main(int argc, char *argv[])
{
// Test gui widgets - 2 Spinboxes and 1 Combobox
QApplication a(argc, argv);
TestSpinBox testSpinBoxObj;
TestComboBox testComboBoxObj;
QTest::qExec(&testComboBoxObj, argc,argv);
QTest::qExec(&testSpinBoxObj, argc,argv);
return 0;
}
I get the output in the console:
Starting D:\Projects\Qt Learning\TestGui (1)\TestGui\debug\TestGui.exe...
********* Start testing of TestComboBox *********
Config: Using QTest library 4.8.1, Qt 4.8.1
PASS : TestComboBox::initTestCase()
PASS : TestComboBox::testComboBoxStepUp()
PASS : TestComboBox::testComboBoxStepDown()
PASS : TestComboBox::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
********* Finished testing of TestComboBox *********
********* Start testing of TestSpinBox *********
Config: Using QTest library 4.8.1, Qt 4.8.1
PASS : TestSpinBox::initTestCase()
PASS : TestSpinBox::testSpinBoxStepUp()
PASS : TestSpinBox::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of TestSpinBox *********
D:\Projects\Qt Learning\TestGui (1)\TestGui\debug\TestGui.exe exited with code 0
How to get the same in a single text file
There is -o filename option to specify output file. For each test object you can redirect output to own file and later concatenate them together.
QList<QObject *> objects;
objects << new TestSpinBox << new TestComboBox;
QString result;
foreach (QObject *o, objects) {
QTemporaryFile f;
f.open();
QStringList args = app.arguments();
args << "-o" << f.fileName();
QTest::qExec(o, args);
result += "\r\n" + f.readAll();
}
qDeleteAll(objects);
Related
Simpe Qt App 'untitled.exe':
#include <QCoreApplication>
#include <QtCore>
void parseCmd(const QCoreApplication &app)
{
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
parser.process(app);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QCoreApplication::setApplicationVersion("1.0");
parseCmd(a);
return a.exec();
}
Output:
2020-12-15T19:06:08.1133300Z ##[section]Starting: Test code...
2020-12-15T19:06:08.1434531Z ==============================================================================
2020-12-15T19:06:08.1434873Z Task : Command line
2020-12-15T19:06:08.1435197Z Description : Run a command line script using Bash on Linux and macOS and cmd.exe on Windows
2020-12-15T19:06:08.1435509Z Version : 2.178.0
2020-12-15T19:06:08.1436101Z Author : Microsoft Corporation
2020-12-15T19:06:08.1436429Z Help : https://learn.microsoft.com/azure/devops/pipelines/tasks/utility/command-line
2020-12-15T19:06:08.1436801Z ==============================================================================
2020-12-15T19:06:09.5889046Z Generating script.
2020-12-15T19:06:09.6545863Z ========================== Starting Command Output ===========================
2020-12-15T19:06:09.7002067Z ##[command]"C:\windows\system32\cmd.exe" /D /E:ON /V:OFF /S /C "CALL "D:\a\_temp\cc51db57-c085-4347-873f-ed28f0e7af53.cmd""
2020-12-15T19:06:09.7240225Z 1 file(s) copied.
2020-12-15T19:06:09.7388204Z Volume in drive D is Temp
2020-12-15T19:06:09.7389142Z Volume Serial Number is 405E-826C
2020-12-15T19:06:09.7389470Z
2020-12-15T19:06:09.7389984Z Directory of D:\a\1\s\Release_WIN_x64
2020-12-15T19:06:09.7390270Z
2020-12-15T19:06:09.7390552Z 12/15/2020 07:06 PM <DIR> .
2020-12-15T19:06:09.7392444Z 12/15/2020 07:06 PM <DIR> ..
2020-12-15T19:06:09.7393989Z 12/15/2020 07:06 PM <DIR> bearer
2020-12-15T19:06:09.7394372Z 12/15/2020 07:06 PM <DIR> iconengines
2020-12-15T19:06:09.7394795Z 12/15/2020 07:06 PM <DIR> imageformats
2020-12-15T19:06:09.7395287Z 12/08/2020 02:33 PM 3,409,920 libcrypto-1_1-x64.dll
2020-12-15T19:06:09.7395702Z 12/08/2020 02:33 PM 682,496 libssl-1_1-x64.dll
2020-12-15T19:06:09.7399561Z 12/15/2020 07:06 PM 47,104 MainApp-1.0.0.exe
2020-12-15T19:06:09.7400169Z 12/15/2020 07:06 PM 594,944 MaintenanceTool-1.0.0.exe
2020-12-15T19:06:09.7400757Z 12/15/2020 07:06 PM <DIR> platforms
2020-12-15T19:06:09.7401416Z 05/11/2020 08:46 AM 5,998,712 Qt5Core.dll
2020-12-15T19:06:09.7401954Z 05/11/2020 08:47 AM 7,085,176 Qt5Gui.dll
2020-12-15T19:06:09.7404188Z 05/11/2020 08:47 AM 1,349,240 Qt5Network.dll
2020-12-15T19:06:09.7404743Z 05/11/2020 03:05 PM 329,848 Qt5Svg.dll
2020-12-15T19:06:09.7405277Z 05/11/2020 08:47 AM 5,516,920 Qt5Widgets.dll
2020-12-15T19:06:09.7405719Z 12/15/2020 07:06 PM <DIR> styles
2020-12-15T19:06:09.7406168Z 12/15/2020 07:05 PM 10,240 untitled.exe
2020-12-15T19:06:09.7406588Z 10 File(s) 25,024,600 bytes
2020-12-15T19:06:09.7407293Z 7 Dir(s) 11,920,207,872 bytes free
2020-12-15T19:06:09.8738006Z ##[error]Cmd.exe exited with code '-1073741701'.
2020-12-15T19:06:09.9214918Z ##[section]Finishing: Test code...
And i try to run that in azure pipeline but is failed to run with -v params (that should show version number).
As all dlls are in place, application is crashing, as also others apps are similar like in example 'MainApp' and 'MaintenanceTool' - using same 'core' stuff.
When i run 'MainApp' or 'MaintenanceTool' - azure wait forever, and only way to stop pipeline is cancel.
Weird thing is that i'm able to compile, but not run :/
Found problem, that is for Azure pipelines.
As Qt class QCommandLineParser is using 'qApp' that can be Core, or GUI app (in my case was QApplication) - when we run app with -v (default option for QCommandLineParser) was/is show MessageBox (just show GUI pop-up) with version.
So when we use that in 'command line pipe line' - there no way that we can click Ok on taht dialog.
Based on code from Can I use QCommandLineParser to determine GUI mode or CLI mode?
i made some example that is just print version - as this was requirement for me :)
#include <QApplication>
#include <QMessageBox>
#include <iostream> // For std::
#include <QtCore>
// Use any other flags from cmd ...
struct Options {
bool version = false;
};
Options parseCmd()
{
QCommandLineParser parser;
// parser.addVersionOption(); DO NOT USE THIS!
QCommandLineOption showVer("ver", "Show program version.");
parser.addOption(showVer);
// Procs args
parser.process(*qApp);
// Save data
Options opts = {};
opts.version = parser.isSet(showVer);
return opts;
}
int main(int argc, char *argv[])
{
std::cout << "Create core app" << std::endl;
QCoreApplication::setApplicationVersion("1.0");
// Create base core app
QScopedPointer<QCoreApplication> app(new QCoreApplication(argc, argv));
// Parse cmd parameters
std::cout << "Parse cmd params" << std::endl;
Options opt = parseCmd();
// If only display version
if(opt.version)
{
std::cout << "Display version" << std::endl;
std::cout << qApp->applicationVersion().toStdString() << std::endl;
} else
{
std::cout << "Run in gui mode" << std::endl;
// Reset app to GUI mode
app.reset();
app.reset(new QApplication(argc, argv));
}
//
if(qobject_cast<QApplication*>(qApp))
{
std::cout << "Show QMessageBox" << std::endl;
// Create any GUI stuff here
QMessageBox::information(nullptr, "Hello", "Hello, World!");
}
else
{
std::cout << "Send quit message" << std::endl;
// As we run QCoreApplication only for parsing cmd params ...
QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection);
}
//
return app->exec();
}
Also we need and extra line into project file: '''CONFIG += console'''
QT += core gui widgets
CONFIG += console
TEMPLATE = app
SOURCES += main.cpp
And that how we can 'show' version in command line ;)
Our team uses Qt Creator. We have quite large projects, and for each project we have a test project with a test class for every production class. The main of the test project calls QTest::qExec for every test class. We run our tests by simply running the test project.
That works fine, but the output is limited to the console, looking like this:
********* Start testing of TestClass1 *********
Config: Using QtTest library 5.10.1, Qt 5.10.1 (x86_64-little_endian-lp64 shared (dynamic) debug build; by GCC 7.3.0)
PASS : TestClass1::initTestCase()
PASS : TestClass1::test1()
PASS : TestClass1::test2()
PASS : TestClass1::cleanupTestCase()
Totals: 2 passed, 0 failed, 0 skipped, 0 blacklisted, 3ms
********* Finished testing of TestClass1*********
********* Start testing of TestClass2 *********
Config: Using QtTest library 5.10.1, Qt 5.10.1 (x86_64-little_endian-lp64 shared (dynamic) debug build; by GCC 7.3.0)
PASS : TestClass2::initTestCase()
PASS : TestClass2::test1()
PASS : TestClass2::test2()
PASS : TestClass2::cleanupTestCase()
Totals: 2 passed, 0 failed, 0 skipped, 0 blacklisted, 3ms
********* Finished testing of TestClass2*********
I just discovered the integrated test gui and would really like to use it. However, I can't get the scanner (Tools->Tests->Rescan tests) to find all tests in a project. If I write this in the main it works fine for TestClass1:
int main(int argc, char** argv) {
TestClass1 test;
return QTest::qExec(&test, argc, argv);
}
However, if I try to add TestClass2 as below the scanner only recognizes TestClass2:
int main(int argc, char** argv) {
TestClass1 test1;
int ret = QTest::qExec(&test1, argc, argv);
TestClass2 test2;
int ret &= QTest::qExec(&test2, argc, argv);
return ret;
}
It seem as if the scanner only finds the last class executed with QTest::qExec.
Any idea how I could get it to find all tests, without touching the existing test classes?
For some time, I can't debug anymore my application which crashes each time I launch it in debug mode. On the other hand, it runs fine when only execute it.
I did many tests with different configurations:
Windows 7 with mingw32
Windows 10 with mingw32
Ubuntu with gcc
On Linux, no problem, everything works fine.
On windows 7 or 10 the application crashes as soon as it is opened, with the following message (twice) :
Microsoft Visual C++ Runtime Library :
This application has requested the Runtime to terminate it in an unusual way.
The output panel gives the following information:
Debugging starts
section .gnu_debuglink not found in ...\build-Integration GspvMapviewer-Desktop_Qt_5_9_2_MinGW_32bit-Debug\debug\Integration GspvMapviewer.exe.debug
QML debugging is enabled. Only use this in a safe environment.
QML Debugger: Waiting for connection on port 49727...
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.
ASSERT: "!m_thread.isRunning()" in file qqmldebugserver.cpp, line 655
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.
Debugging has finished
No problem mentioned in compilation output.
Searching the net, I can't find track on the origin of the problem.
Would you know how to solve this point ?
Thank you in advance.
EDIT1
.pro
QT += quick positioning widgets
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
waypointsfilter.cpp
RESOURCES += gspv.qrc \
resource.qrc
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
waypointsfilter.h \
waypointsmodel.h \
airport.h \
waypoint.h \
airportsmodel.h \
landmark.h \
runwaymodel.h
OTHER_FILES +=main.qml \
helper.js \
images/marker.png \
images/scale.png \
images/scale_end.png \
map/MapComponent.qml \
map/Marker.qml \
map/MapSliders.qml \
menus/MainMenu.qml \
forms/Message.qml \
forms/MessageForm.ui.qml
DISTFILES += \
forms/SplitInterface.qml \
forms/IME.qml \
map/SimpleMap.qml \
map/Airport.qml \
images/BlackWaypoint.bmp \
map/Runway.qml \
forms/InitialAirport.qml
main.cpp
#include "waypointsmodel.h"
#include "waypointsfilter.h"
#include "airportsmodel.h"
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QDebug>
#include <QDateTime>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
WaypointsModel model;
AirportsModel apModel;
QVariantMap parameters;
parameters[QStringLiteral("esri.useragent")] = QStringLiteral("Générateur Simplifié de Plans de Vol");
model.readFromCSV(QCoreApplication::applicationDirPath() + "/files/Waypoints.txt");
apModel.readFromTXT(QCoreApplication::applicationDirPath() + "/files/Airports.txt");
WaypointsFilter proxyModel(&model);
QQmlApplicationEngine engine;
engine.addImportPath(QStringLiteral(":/imports"));
engine.rootContext()->setContextProperty("waypointsFilter", &proxyModel);
engine.rootContext()->setContextProperty("airportsModel", &apModel);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QObject::connect(&engine, SIGNAL(quit()), qApp, SLOT(quit()));
QObject *item = engine.rootObjects().first();
Q_ASSERT(item);
// The crash occurs after this line is executed
QMetaObject::invokeMethod(item, "initializeProviders",
Q_ARG(QVariant, QVariant::fromValue(parameters)));
return app.exec();
}
EDIT2
Here is snippet of the faulty code :
function getPlugins()
{
//crash is here !
var plugin = Qt.createQmlObject ('import QtLocation 5.6; Plugin {}', mainWindow)
var myArray = new Array()
for (var i = 0; i<plugin.availableServiceProviders.length; i++) {
var tempPlugin = Qt.createQmlObject ('import QtLocation 5.6; Plugin {name: "' + plugin.availableServiceProviders[i]+ '"}', mainWindow)
if (tempPlugin.supportsMapping()
&& !(tempPlugin.name === "itemsoverlay")
&& !(tempPlugin.name === "here")
&& !(tempPlugin.name === "mapbox")
&& !(tempPlugin.name === "mapboxgl"))
myArray.push(tempPlugin.name)
}
myArray.sort()
return myArray
}
function initializeProviders(pluginParameters)
{
var parameters = new Array()
for (var prop in pluginParameters){
var parameter = Qt.createQmlObject('import QtLocation 5.6; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}',mainWindow)
console.log ("plugin name :" + prop + "value : " +pluginParameters[prop] )
parameters.push(parameter)
}
mainWindow.parameters = parameters
var plugins = getPlugins()
mainMenu.providerMenu.createMenu(plugins)
for (var i = 0; i<plugins.length; i++) {
if (plugins[i] === "esri")
mainMenu.selectProvider(plugins[i]) //Génère la création de la carte par déclenchement de onSelectProvider
}
}
My concern is this warning : section .gnu_debuglink not found
Why this section is not found into the .exe.debug file ?
Here is the status of debugging just before crash :
and after crash :
.gnu_debuglink is a mechanism that gdb uses to relate the separate debug info to the actual binary. It might be related to the crash caused by plugin loading or gdb might just inform it related to some other plugin being loaded.
I suspect your problem could be related to OOM caused by a bug in Qt which will be fixed in Qt 5.9.5. Meanwhile, you could test if stripping the qtgeoservices_mapboxgld.dll file prevents the crash (that was said in the bugreport comments).
Also, to make your code more robust you should catch exceptions from Qt.createQmlObject because if plugin loading fails it throws error:
try {
var newObject = Qt.createQmlObject('import QtLocation 5.6; ...);
} catch (error) {
console.log ("Error loading QML : ")
for (var i = 0; i < error.qmlErrors.length; i++) {
console.log("lineNumber: " + error.qmlErrors[i].lineNumber)
console.log("columnNumber: " + error.qmlErrors[i].columnNumber)
console.log("fileName: " + error.qmlErrors[i].fileName)
console.log("message: " + error.qmlErrors[i].message)
}
}
"Invalid parameter passed to C runtime function." warning messages a probably caused by Qt calling some C runtime functions when the above mentioned bug hits.
Generic solution for tracking "Invalid parameter passed to C runtime function." origins has been provided by Dennis Yurichev. Below, I provide you steps how to do it (paths from my env).
QML:
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
function initializeProviders(anObject) {
for (var prop in anObject) {
console.log("Object item:", prop, "=", anObject[prop])
}
}
Text {
id: textLabel
anchors.centerIn: parent
text: qsTr("text")
}
}
C++:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QVariantMap parameters;
parameters[QStringLiteral("esri.useragent")] = QStringLiteral("Générateur Simplifié de Plans de Vol");
QObject *item = engine.rootObjects().first();
Q_ASSERT(item);
QMetaObject::invokeMethod(item, "initializeProviders",
Q_ARG(QVariant, QVariant::fromValue(parameters)));
// Generate error: file open fails
FILE *pFile = fopen (NULL,"w");
// fputs with invalid file displays in debug mode "Invalid parameter passed to C runtime function."
fputs("abc",pFile);
// fprintf with invalid file crashes the program
fprintf(pFile, "def\n");
return app.exec();
}
In command prompt:
C:\> SET PATH=%PATH%;C:\Qt\5.9.4\mingw53_32\bin
C:\> cd proj\build-quickTest-Desktop_Qt_5_9_4_MinGW_32bit-Debug\debug
C:\> C:\Qt\Tools\mingw530_32\bin\gdb quickTest.exe
(gdb) break OutputDebugStringA
Function "OutputDebugStringA" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (OutputDebugStringA) pending.
(gdb) r
when it breaks print the backtrace
(gdb) bt
<backtrace...>
(gdb) c
Continuing.
warning: QML debugging is enabled. Only use this in a safe environment.
(gdb) bt
<backtrace...>
(gdb) c
Continuing.
warning: qml: Object item: esri.useragent = Générateur Simplifié de Plans de Vol
Breakpoint 1, 0x74d535fc in OutputDebugStringA ()
from C:\Windows\syswow64\KernelBase.dll
(gdb) bt
#0 0x74d535fc in OutputDebugStringA ()
from C:\Windows\syswow64\KernelBase.dll
#1 0x754569c4 in msvcrt!_chkesp () from C:\Windows\syswow64\msvcrt.dll
#2 0x754569d0 in msvcrt!_chkesp () from C:\Windows\syswow64\msvcrt.dll
#3 0x00010001 in ?? ()
#4 0x7543b9b7 in msvcrt!_ftol2_sse_excpt ()
from C:\Windows\syswow64\msvcrt.dll
#5 0x00000000 in ?? ()
(gdb) c
Continuing.
warning: Invalid parameter passed to C runtime function.
Program received signal SIGSEGV, Segmentation fault.
0x770a2302 in ntdll!RtlEnterCriticalSection ()
from C:\Windows\SysWOW64\ntdll.dll
(gdb) bt
#0 0x770a2302 in ntdll!RtlEnterCriticalSection ()
from C:\Windows\SysWOW64\ntdll.dll
#1 0x004080b7 in _lock_file ()
#2 0x00402fe1 in __mingw_vfprintf ()
#3 0x00401656 in fprintf (__stream=0x0,
__format=0x40b1fa <qMain(int, char**)::{lambda()#3}::operator()() const::qst
ring_literal+154> "def\n")
at C:/Qt/Tools/mingw530_32/i686-w64-mingw32/include/stdio.h:289
#4 0x00401b2d in qMain (argc=1, argv=argv#entry=0x21319038)
at ..\quickTest\main.cpp:32
#5 0x00402f05 in WinMain#16 () at qtmain_win.cpp:104
#6 0x0040949d in main ()
(gdb)
We can see that fprintf is called with __stream=0x0 which causes the segfault.
Edit:
I did some testing with mapviewer example. When I ran it in debug mode:
Debugging starts
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
QMutex: destroying locked mutex
Debugging has finished
Then I stripped qtgeoservices_mapboxgld.dll:
C:\...> SET PATH=C:\Qt\5.9.4\mingw53_32\bin;%PATH%
C:\...> cd C:\Qt\5.9.4\mingw53_32\plugins\geoservices
C:\...> strip qtgeoservices_mapboxgld.dll
After stripping running in debug mode succeeded:
qml: initializeProviders: osm.useragent = QtLocation Mapviewer example
qml: getPlugins: esri
qml: getPlugins: pushing esri
qml: getPlugins: mapbox
qml: getPlugins: pushing mapbox
qml: getPlugins: mapboxgl
qml: getPlugins: pushing mapboxgl
qml: getPlugins: here
qml: getPlugins: pushing here
qml: getPlugins: itemsoverlay
qml: getPlugins: pushing itemsoverlay
qml: getPlugins: osm
qml: getPlugins: pushing osm
This way you should at least get forward in your app bug hunting.
Which Qt and mingw version are you using?
Well, I tried it all. This should be very simple, yet I am stock at finding out what in the world is going on with my foreach. It just don't help.
#include <QCoreApplication>
//coreapplication or Qapplication the error is there
#include <QList>
#include <QDebug>
int main()
{
QList<int> list;
list << 1 << 2 << 3 << 4 << 5;
foreach (int i, list) //expected token ';' got 'int'.
{
qDebug() << i;
}
}
/*
QT += core gui
TARGET = QtTest
CONFIG += console
CONFIG -= app_bundle
CONFIG += no_keywords
TEMPLATE = app
SOURCES += main.cpp
*/
You specified no_keywords in your config. You have to use Q_FOREACH instead of foreach. See the documentation for foreach.
That being said, I would switch to the C++11 range-based for, since it doesn't have issues with commas in types. For example,
Q_FOREACH (QPair<int, int> p, pairList)
won't compile since the preprocessor thinks you're trying to invoke the macro with 3 arguments instead of 2.
Instead you can use the C++11 for(:):
for(int i:list)
{
qDebug() << i;
}
Note that you will have to compile with the C++-11 flag, therefore add this line to your project file:
QMAKE_CXXFLAGS += -std=c++11
Note that the C++11 for is more efficient than the Qt foreach as indicated by: Qt foreach loop ordering vs. for loop for QList
Edit:
Like commented by Frank Osterfeld you can also use:
CONFIG+=c++11
in your .pro file since Qt 5.4 as commented here: How to use C++11 in your Qt Projects.
I am trying to figure out the use of QProcess. I looked at Qt doc with no luck.
http://doc.qt.io/qt-4.8/qprocess.html
EXAMPLES OF PROBLEM.
Example 1: Code bellow works.
#include <QtCore/QCoreApplication>
#include <QTextStream>
#include <QByteArray>
#include <QProcess>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTextStream qout(stdout);
QProcess cmd;
cmd.start("cmd");
if (!cmd.waitForStarted()) {
return false;
}
cmd.waitForReadyRead();
QByteArray result = cmd.readAll();
//qout << result.data() << endl; //console junk captured, doesn't show.
//My test command
cmd.write("echo hello");
cmd.write("\n");
//Capture my result
cmd.waitForReadyRead();
//This is my command shown by cmd, I don't show it, capture & discard it.
result = cmd.readLine();
//Read result of my command ("hello") and the rest of output like cur dir.
result = cmd.readAll();
qout << result.data();
qout << "\n\n---End, bye----" << endl;
return a.exec();
}
The output of the above code is
hello
F:\Dev_Qt\expControllingExtConsoleApps-build-desktop>
---End, bye----
The problem is that if I try to use ipconfig or 7zip in this fashion via Qprocess and cmd console, I am unable to see any output from ipconfig or 7zip. I don't know if anything is even done, if something is done then why can't I see the output? Code below illustrates.
Example 2: Does not work. Can't use ipconfig.
#include <QtCore/QCoreApplication>
#include <QTextStream>
#include <QByteArray>
#include <QString>
#include <QProcess>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTextStream qout(stdout);
QProcess cmd2;
cmd2.setWorkingDirectory("C:/Program Files/7-Zip"); //not needed in this example.
cmd2.setReadChannel(QProcess::StandardOutput);
cmd2.setProcessChannelMode(QProcess::MergedChannels);
cmd2.start("cmd");
if (!cmd2.waitForStarted())
{
qout << "Error: Could not start!" << endl;
return false;
}
cmd2.waitForReadyRead();
QByteArray result = cmd2.readAll();
qout << result.data() << endl; //Console version info, etc.
//My command
cmd2.write("ipconfig");
cmd2.write("\n");
//Capture output of ipconfig command
//DOES NOT WORK!!
cmd2.waitForReadyRead();
while (! cmd2.atEnd())
{
result = cmd2.readLine();
qout << result;
result.clear();
}
qout << endl;
qout << "\n\n---end----" << endl;
return a.exec();
}
Output is below, it is missing the ipconfig connection information result. No output from ipconfig is captured at all.
Microsoft Windows XP [Version
5.1.2600] (C) Copyright 1985-2001 Microsoft Corp.
C:\Program Files\7-Zip> ipconfig
---end----
Should have been more like this (with ipconfig result).
Microsoft Windows XP [Version
5.1.2600] (C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and
Settings\noname>ipconfig
Windows IP Configuration
Ethernet adapter Local Area
Connection:
Connection-specific DNS Suffix . :
IP Address. . . . . . . . . . . . : 192.172.148.135
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.172.148.177
C:\Documents and Settings\noname>
Obviously the output should should have been a little diff than above but the Connection info which is the output of "ipconfig" should have been captured. In the same way if I try to use 7zip via cmd console... I can not see/capture any output of 7zip. So my question is how can I use command line apps like ipconfig and 7zip via QProcess and cmd console and see the result of the output of these applications?
Example 3: 7zip does not work
#include <QtCore/QCoreApplication>
#include <QTextStream>
#include <QByteArray>
#include <QProcess>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTextStream qout(stdout);
QProcess cmd2;
cmd2.setWorkingDirectory("C:/Program Files/7-Zip");
cmd2.setReadChannel(QProcess::StandardOutput);
cmd2.setProcessChannelMode(QProcess::MergedChannels);
cmd2.start("cmd");
if (!cmd2.waitForStarted()) {
return false;
}
//My Command
cmd2.write("7z.exe");
cmd2.write("\n");
//Capture output of ipconfig command
cmd2.waitForReadyRead();
QByteArray result;
while (! cmd2.atEnd()) {
result = cmd2.readLine();
qout << result;
result.clear();
}
qout << endl;
qout << "\n\n---end----" << endl;
return a.exec();
}
Output below. Does not show anything from 7zip.
Microsoft Windows XP [Version
5.1.2600] (C) Copyright 1985-2001 Microsoft Corp.
C:\Program Files\7-Zip>7z.exe
---end----
Output is expected to be along the lines of...
Microsoft Windows XP [Version
5.1.2600] (C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\noname>cd
C:\Program Files\7-Zip
C:\Program Files\7-Zip>7z.exe
7-Zip 9.15 beta Copyright (c)
1999-2010 Igor Pavlov 2010-06-20
Usage: 7z [...]
<archive_name> [<file_names>...]
[<#listfiles...>]
a: Add files to archive
b: Benchmark d: Delete files from
archive e: Extract files from
archive (without using directory
names) l: List contents of archive
t: Test integrity of archive u:
Update files to archive x: eXtract
files with full paths
-ai[r[-|0]]{#listfile|!wildcard}: Include archives
-ax[r[-|0]]{#listfile|!wildcard}: eXclude archives -bd: Disable
percentage indicator
-i[r[-|0]]{#listfile|!wildcard}: Include filenames -m{Parameters}:
set compression Method
-o{Directory}: set Output directory -p{Password}: set Password -r[-|0]: Recurse subdirectories -scs{UTF-8 |
WIN | DOS}: set charset for list files
-sfx[{name}]: Create SFX archive -si[{name}]: read data from stdin -slt: show technical information for l (List) command -so: write data to
stdout -ssc[-]: set sensitive case
mode -ssw: compress shared files
-t{Type}: Set type of archive -u[-][p#][q#][r#][x#][y#][z#][!newArchiveName]:
Update options -v{Size}[b|k|m|g]:
Create volumes -w[{path}]: assign
Work directory. Empty path means a
temporary directory
-x[r[-|0]]]{#listfile|!wildcard}: eXclude filenames -y: assume Yes on
all queries
C:\Program Files\7-Zip>
eI see one big problem.
Under windows you issue a commend pressing the Enter key. Writing
cmd.write("command");
cmd.write("\n");
just isn't enough you have to write
cmd.write("command");
cmd.write("\n\r");
Notice the trailing \r. Try this, it should work better, and by better I mean 7zip. I don't know if you'll get ipconfig to work properly.
Good luck and best regards
D
EDIT
Here is a working solution:
#include <QtCore/QCoreApplication>
#include <QtCore/QProcess>
#include <QtCore/QString>
#include <QtCore/QTextStream>
// Not clean, but fast
QProcess *g_process = NULL;
// Needed as a signal catcher
class ProcOut : public QObject
{
Q_OBJECT
public:
ProcOut (QObject *parent = NULL);
virtual ~ProcOut() {};
public slots:
void readyRead();
void finished();
};
ProcOut::ProcOut (QObject *parent /* = NULL */):
QObject(parent)
{}
void
ProcOut::readyRead()
{
if (!g_process)
return;
QTextStream out(stdout);
out << g_process->readAllStandardOutput() << endl;
}
void
ProcOut::finished()
{
QCoreApplication::exit (0);
}
int main (int argc, char **argv)
{
QCoreApplication *app = new QCoreApplication (argc, argv);
ProcOut *procOut = new ProcOut();
g_process = new QProcess();
QObject::connect (g_process, SIGNAL(readyReadStandardOutput()),
procOut, SLOT(readyRead()));
QObject::connect (g_process, SIGNAL(finished (int, QProcess::ExitStatus)),
procOut, SLOT(finished()));
g_process->start (QLatin1String ("cmd"));
g_process->waitForStarted();
g_process->write ("ipconfig\n\r");
// Or cmd won't quit
g_process->write ("exit\n\r");
int result = app->exec();
// Allright, process finished.
delete procOut;
procOut = NULL;
delete g_process;
g_process = NULL;
delete app;
app = NULL;
// Lets us see the results
system ("pause");
return result;
}
#include "main.moc"
Hope that helps. It worked everytime on my machine.
Even though Dariusz Scharsig already provided a solution to the problem, I would like to point out what I believe to be the actual problem(s) which can be solved using the signal slot mechanism.
Problem 1. The condition in your while loop is based on bool QProcess::atEnd () const which is according to QProcess Documentation states:
Reimplemented from QIODevice::atEnd().
Returns true if the process is
not running, and no more data is available for reading; otherwise
returns false.
But if you looking the documentation for QIODevice::atEnd(), it states:
Returns true if the current read and write position is at the end of
the device (i.e. there is no more data available for reading on the
device); otherwise returns false.
For some devices, atEnd() can return
true even though there is more data to read. This special case only
applies to devices that generate data in direct response to you
calling read() (e.g., /dev or /proc files on Unix and Mac OS X, or
console input / stdin on all platforms).
Solution 1. Change the while loop condition to check the state of your process: while(cmd2.state()!=QProcess::NotRunning){.
Problem 2. You use cmd2.waitForReadyRead(); outside of the loop. Perhaps some data is ready for reading now and when you finished reading, some more gets made available:
you read the commands you just wrote : ipconfig\n
ipconfig takes some time to start up and send text to the console. But by then you have already exited your loop because atEnd() gave true even though your process is still running.
Solution 2. place the waitForReadyRead() inside your loop.
Consequence 2. waitForReadyRead() will tell you when there is data available, which could be more than one Line, so you should consequently also change the cmd2.ReadLine() to cmd2.ReadAll().
Problem 3. As documented in QProcess::closeWriteChannel()
Closing the write channel is necessary for programs that read input
data until the channel has been closed.
Solution 3. One of the following options should work when finished writing your inputs
End the process: cmd2.write("exit\n");
close the Writechannel: cmd2.closeWriteChannel();
Working code:
#include <QtCore/QCoreApplication>
#include <QTextStream>
#include <QByteArray>
#include <QString>
#include <QProcess>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTextStream qout(stdout);
QByteArray result;
QProcess cmd2;
cmd2.setReadChannel(QProcess::StandardOutput);
cmd2.setProcessChannelMode(QProcess::MergedChannels);
cmd2.start("cmd");
if (!cmd2.waitForStarted()){
qout << "Error: Could not start!" << endl;
return 0;
}
cmd2.write("ipconfig\n");
cmd2.closeWriteChannel(); //done Writing
while(cmd2.state()!=QProcess::NotRunning){
cmd2.waitForReadyRead();
result = cmd2.readAll();
qout << result;
}
qout << endl << "---end----" << endl;
return a.exec();
}
I wrote this answer just to explain the way I understand your problem and found a solution but would like to emphasize that the Preferable Solution is to use the Signal/Slot Mechanism as presented by Dariusz.