Subversion Repositories tpanel

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
325 andreas 1
/*
2
 * Copyright (C) 2023 by Andreas Theofilu <andreas@theosys.at>
3
 *
4
 * This program is free software; you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program; if not, write to the Free Software Foundation,
16
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
17
 */
18
#include <cstring>
326 andreas 19
#include <iomanip>
20
#include <fstream>
325 andreas 21
 
22
#include "testmode.h"
23
#include "tpagemanager.h"
326 andreas 24
#include "tdirectory.h"
325 andreas 25
#include "tconfig.h"
326 andreas 26
#include "tresources.h"
325 andreas 27
#include "terror.h"
328 andreas 28
#ifdef __ANDROID__
29
#include <android/log.h>
30
#endif
325 andreas 31
 
334 andreas 32
#define TIMEOUT     5000000         // Microseconds (5 seconds)
33
#define DELAY       100             // Wait this amount of microseconds
326 andreas 34
 
35
using std::string;
325 andreas 36
using std::strncpy;
37
using std::min;
326 andreas 38
using std::atomic;
39
using std::vector;
40
using std::cout;
41
using std::endl;
42
using std::ifstream;
43
using namespace dir;
325 andreas 44
 
334 andreas 45
bool __success{false};              // TRUE indicates a successful test
46
bool __done{false};                 // TRUE marks a test case as done
47
bool _test_screen{false};           // TRUE: The graphical part of a test case is done.
48
bool _testmode{false};              // TRUE: Test mode is active. Activated by command line.
49
bool _run_test_ready{false};        // TRUE: The test cases are allowd to start.
50
string gLastCommand;                // The last command to be tested.
51
_TestMode *_gTestMode{nullptr};     // Pointer to class _TestMode. This class must exist only once!
326 andreas 52
 
53
extern amx::TAmxNet *gAmxNet;
54
 
55
_TestMode::_TestMode(const string& path)
56
    : mPath(path)
57
{
325 andreas 58
#if TESTMODE == 1
326 andreas 59
    DECL_TRACER("_TestMode::_TestMode(const string& path)");
325 andreas 60
 
326 andreas 61
    TDirectory dir(mPath);
62
    dir.scanFiles(".tst");
63
    size_t num = dir.getNumEntries();
64
    size_t pos = 0;
65
 
66
    while (pos < num)
67
    {
68
        DFILES_T f = dir.getEntry(pos);
69
 
70
        if (dir.testText(f.attr))
71
            mCmdFiles.push_back(f.name);
72
 
73
        pos++;
74
    }
325 andreas 75
#endif
76
}
77
 
326 andreas 78
_TestMode::~_TestMode()
325 andreas 79
{
326 andreas 80
    DECL_TRACER("_TestMode::~_TestMode()");
81
 
82
    _gTestMode = nullptr;
83
    gLastCommand.clear();
84
    prg_stopped = true;
85
    killed = true;
86
 
87
    if (gAmxNet)
88
        gAmxNet->stop();
89
 
90
    if (gPageManager)
91
        gPageManager->callShutdown();
92
}
93
 
94
void _TestMode::run()
95
{
96
#if TESTMODE == 1
97
    DECL_TRACER("_TestMode::run()");
98
 
99
    if (isRunning)
100
        return;
101
 
102
    try
103
    {
104
        mThread = std::thread([=] { this->start(); });
105
        mThread.detach();
106
    }
107
    catch (std::exception& e)
108
    {
109
        MSG_ERROR("Error spawning thread: " << e.what());
110
    }
111
#endif
112
}
113
 
114
void _TestMode::start()
115
{
116
    isRunning = true;
117
    DECL_TRACER("_TestMode::start()");
118
 
330 andreas 119
    while (!_run_test_ready)
120
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
121
 
326 andreas 122
    vector<string>::iterator iter;
123
 
124
    for (iter = mCmdFiles.begin(); iter != mCmdFiles.end(); ++iter)
125
    {
126
        _TESTCMD tcmd;
127
 
128
        try
129
        {
130
            ifstream is(*iter);
131
            char buffer[4096];
327 andreas 132
            int line = 1;
330 andreas 133
            int port = 1;
134
            string command, content;
326 andreas 135
            tcmd.command.clear();
136
            tcmd.result.clear();
137
            tcmd.compare = false;
138
 
139
            while (is.getline(buffer, sizeof(buffer)))
140
            {
334 andreas 141
                if (buffer[0] == '#' || buffer[0] == '\0')
142
                {
143
                    line++;
326 andreas 144
                    continue;
334 andreas 145
                }
326 andreas 146
 
330 andreas 147
                string scmd(buffer);
148
                command.clear();
149
                content.clear();
150
                size_t pos = scmd.find("=");
151
 
152
                // Parse the command line and extract the command and the content
153
                if (scmd == "exec")
154
                    command = scmd;
155
                else if (pos == string::npos)
326 andreas 156
                {
334 andreas 157
                    MSG_WARNING("(" << *iter << ") Line " << line << ": Invalid or malformed command!");
158
                    line++;
330 andreas 159
                    continue;
160
                }
161
                else
162
                {
163
                    command = scmd.substr(0, pos);
164
                    content = scmd.substr(pos + 1);
165
                }
326 andreas 166
 
330 andreas 167
                // Test for the command and take the desired action.
168
                if (command == "command")
169
                    tcmd.command = content;
170
                else if (command == "port")
326 andreas 171
                {
330 andreas 172
                    int p = atoi(content.c_str());
326 andreas 173
 
330 andreas 174
                    if (p > 0)
175
                        port = p;
326 andreas 176
                }
330 andreas 177
                else if (command == "result")
178
                    tcmd.result = content;
179
                else if (command == "compare")
180
                    tcmd.compare = isTrue(content);
334 andreas 181
                else if (command == "reverse")
182
                    tcmd.reverse = isTrue(content);
330 andreas 183
                else if (command == "wait")
184
                {
185
                    unsigned long ms = atol(content.c_str());
326 andreas 186
 
330 andreas 187
                    if (ms != 0)
188
                        std::this_thread::sleep_for(std::chrono::milliseconds(ms));
326 andreas 189
                }
331 andreas 190
                else if (command == "nowait")
191
                    tcmd.nowait = isTrue(content);
335 andreas 192
                else if (command == "screenwait")
193
                    tcmd.waitscreen = isTrue(content);
330 andreas 194
                else if (command == "exec")
327 andreas 195
                {
196
                    if (tcmd.command.empty())
197
                    {
334 andreas 198
                       MSG_WARNING("(" << *iter << ") Nothing to execute on line " << line << "!");
327 andreas 199
                       line++;
200
                       continue;
201
                    }
202
 
334 andreas 203
                    mCaseNumber++;
335 andreas 204
                    inform("\x1b[1;37mCase " + intToString(mCaseNumber) + "\x1b[0m: \x1b[0;33m" + tcmd.command + "\x1b[0m");
332 andreas 205
                    __done = false;
206
                    __success = false;
334 andreas 207
                    _test_screen = false;
330 andreas 208
                    inject(port, tcmd.command);
327 andreas 209
 
331 andreas 210
                    if (!tcmd.nowait)
211
                        testSuccess(tcmd);
212
                    else
213
                    {
214
                        MSG_WARNING("Skipped result check!");
215
                        inform("   Result check skipped!");
216
                    }
217
 
327 andreas 218
                    tcmd.command.clear();
219
                    tcmd.result.clear();
220
                    tcmd.compare = false;
331 andreas 221
                    tcmd.nowait = false;
334 andreas 222
                    tcmd.reverse = false;
335 andreas 223
                    tcmd.waitscreen = false;
334 andreas 224
                    mVerify.clear();
330 andreas 225
                    port = 1;
327 andreas 226
                }
227
 
228
                line++;
326 andreas 229
            }
230
 
231
            is.close();
232
        }
233
        catch (std::exception& e)
234
        {
235
            MSG_ERROR("Error on file " << *iter << ": " << e.what());
236
            continue;
237
        }
238
    }
239
 
240
    isRunning = false;
241
    delete this;
242
}
243
 
244
void _TestMode::inject(int port, const string& c)
245
{
325 andreas 246
    if (!gPageManager)
247
        return;
248
 
326 andreas 249
    gLastCommand = c;
325 andreas 250
    int channel = TConfig::getChannel();
251
    int system = TConfig::getSystem();
252
 
334 andreas 253
    string bef = c;
254
 
255
    if (startsWith(bef, "PUSH-"))
256
    {
257
        amx::ANET_SEND scmd;
258
        size_t pos = bef.find("-");
259
        string bt = bef.substr(pos + 1);
260
        vector<string> parts = StrSplit(bt, ",");
261
 
262
        if (parts.size() < 2)
263
        {
264
            MSG_ERROR("Command PUSH has syntax: PUSH-<x>,<y>!");
265
            return;
266
        }
267
 
268
        mX = atoi(parts[0].c_str());
269
        mY = atoi(parts[1].c_str());
270
        mPressed = true;
271
 
272
        if (gPageManager)
273
            gPageManager->mouseEvent(mX, mY, mPressed);
274
    }
275
    else if (startsWith(bef, "RELEASE-"))
276
    {
277
        amx::ANET_SEND scmd;
278
        size_t pos = bef.find("-");
279
        string bt = bef.substr(pos + 1);
280
        vector<string> parts = StrSplit(bt, ",");
281
 
282
        if (parts.size() < 2)
283
        {
284
            MSG_ERROR("Command RELEASE has syntax: RELEASE-<x>,<y>!");
285
            return;
286
        }
287
 
288
        mX = atoi(parts[0].c_str());
289
        mY = atoi(parts[1].c_str());
290
        mPressed = false;
291
 
292
        if (gPageManager)
293
            gPageManager->mouseEvent(mX, mY, mPressed);
294
    }
335 andreas 295
    else if (startsWith(bef, "CLICK-"))
296
    {
297
        amx::ANET_SEND scmd;
298
        size_t pos = bef.find("-");
299
        string bt = bef.substr(pos + 1);
300
        vector<string> parts = StrSplit(bt, ",");
301
 
302
        if (parts.size() < 2)
303
        {
304
            MSG_ERROR("Command CLICK has syntax: CLICK-<x>,<y>!");
305
            return;
306
        }
307
 
308
        mX = atoi(parts[0].c_str());
309
        mY = atoi(parts[1].c_str());
310
 
311
        if (gPageManager)
312
        {
313
            mPressed = true;
314
            gPageManager->mouseEvent(mX, mY, true);
315
            std::this_thread::sleep_for(std::chrono::milliseconds(200));
316
            mPressed = false;
317
            gPageManager->mouseEvent(mX, mY, false);
318
        }
319
    }
334 andreas 320
    else
321
    {
322
        amx::ANET_COMMAND cmd;
323
        cmd.MC = 0x000c;
324
        cmd.device1 = channel;
325
        cmd.port1 = port;
326
        cmd.system = system;
327
        cmd.data.message_string.system = system;
328
        cmd.data.message_string.device = channel;
329
        cmd.data.message_string.port = port;    // Must be the address port of button
330
        cmd.data.message_string.type = DTSZ_STRING;    // Char string
331
        cmd.data.message_string.length = min(c.length(), sizeof(cmd.data.message_string.content));
332
        memset(cmd.data.message_string.content, 0, sizeof(cmd.data.message_string.content));
333
        memcpy ((char *)cmd.data.message_string.content, c.c_str(), cmd.data.message_string.length);
334
        gPageManager->doCommand(cmd);
335
    }
325 andreas 336
}
326 andreas 337
 
334 andreas 338
void _TestMode::setMouseClick(int x, int y, bool pressed)
339
{
340
    DECL_TRACER("_TestMode::setMouseClick(int x, int y, bool pressed)");
341
 
342
    if (mX == 0 && mY == 0 && mPressed == false)
343
        return;
344
 
345
    if (mX == x && mY == y && mPressed == pressed)
346
    {
347
        __success = true;
348
        _test_screen = true;
349
        __done = true;
350
        mX = mY = 0;
351
        mPressed = false;
352
    }
353
}
354
 
327 andreas 355
void _TestMode::testSuccess(_TESTCMD& tc)
326 andreas 356
{
357
    ulong total = 0;
335 andreas 358
    bool ready = ((tc.compare || tc.waitscreen) ? (__done && _test_screen) : __done);
326 andreas 359
 
334 andreas 360
    while (!ready && total < TIMEOUT)
326 andreas 361
    {
362
        usleep(DELAY);
363
        total += DELAY;
335 andreas 364
        ready = ((tc.compare || tc.waitscreen) ? (__done && _test_screen) : __done);
326 andreas 365
    }
366
 
367
    if (total >= TIMEOUT)
368
    {
369
        MSG_WARNING("Command \"" << gLastCommand << "\" timed out!");
370
#ifdef __ANDROID__
328 andreas 371
        __android_log_print(ANDROID_LOG_INFO, "tpanel", "Command \"%s\" timed out!", gLastCommand.c_str());
326 andreas 372
#else
373
        cout << "Command \"" << gLastCommand << "\" timed out!" << endl;
374
#endif
334 andreas 375
        __success = false;
376
        tc.compare = false;
326 andreas 377
    }
378
 
335 andreas 379
    if (tc.compare && tc.result.compare(mVerify) == 0)
327 andreas 380
    {
334 andreas 381
        MSG_WARNING("The result \"" << mVerify << "\" is equal to \"" << tc.result << "\"!");
382
        __success = tc.reverse ? false : true;
327 andreas 383
    }
384
    else if (tc.compare)
385
    {
334 andreas 386
        MSG_WARNING("The result \"" << mVerify << "\" does not match the expected result of \"" << tc.result << "\"!");
387
        __success = tc.reverse ? true : false;
327 andreas 388
    }
389
 
326 andreas 390
    if (__success)
391
    {
392
        MSG_INFO("SUCCESS (" << gLastCommand << ")");
393
#ifdef __ANDROID__
334 andreas 394
        __android_log_print(ANDROID_LOG_INFO, "tpanel", "SUCCESS");
326 andreas 395
#else
335 andreas 396
        cout << "   \x1b[0;32mSUCCESS\x1b[0m" << endl;
326 andreas 397
#endif
398
    }
399
    else
400
    {
401
        MSG_ERROR("FAILED (" << gLastCommand << ")");
402
#ifdef __ANDROID__
335 andreas 403
        __android_log_print(ANDROID_LOG_ERROR, "tpanel", "FAILED");
326 andreas 404
#else
335 andreas 405
        cout << "   \x1b[0;31mFAILED\x1b[0m" << endl;
326 andreas 406
#endif
407
    }
408
 
409
    __success = false;
334 andreas 410
    _test_screen = false;
326 andreas 411
    __done = false;
412
}
413
 
414
void _TestMode::inform(const string& msg)
415
{
416
    MSG_INFO(msg);
417
#ifdef __ANDROID__
418
    __android_log_print(ANDROID_LOG_ERROR, "tpanel", "%s", msg.c_str());
419
#else
420
    cout << msg << endl;
421
#endif
422
}