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);
330 andreas 192
                else if (command == "exec")
327 andreas 193
                {
194
                    if (tcmd.command.empty())
195
                    {
334 andreas 196
                       MSG_WARNING("(" << *iter << ") Nothing to execute on line " << line << "!");
327 andreas 197
                       line++;
198
                       continue;
199
                    }
200
 
334 andreas 201
                    mCaseNumber++;
202
                    inform("Case " + intToString(mCaseNumber) + ": " + tcmd.command);
332 andreas 203
                    __done = false;
204
                    __success = false;
334 andreas 205
                    _test_screen = false;
330 andreas 206
                    inject(port, tcmd.command);
327 andreas 207
 
331 andreas 208
                    if (!tcmd.nowait)
209
                        testSuccess(tcmd);
210
                    else
211
                    {
212
                        MSG_WARNING("Skipped result check!");
213
                        inform("   Result check skipped!");
214
                    }
215
 
327 andreas 216
                    tcmd.command.clear();
217
                    tcmd.result.clear();
218
                    tcmd.compare = false;
331 andreas 219
                    tcmd.nowait = false;
334 andreas 220
                    tcmd.reverse = false;
221
                    mVerify.clear();
330 andreas 222
                    port = 1;
327 andreas 223
                }
224
 
225
                line++;
326 andreas 226
            }
227
 
228
            is.close();
229
        }
230
        catch (std::exception& e)
231
        {
232
            MSG_ERROR("Error on file " << *iter << ": " << e.what());
233
            continue;
234
        }
235
    }
236
 
237
    isRunning = false;
238
    delete this;
239
}
240
 
241
void _TestMode::inject(int port, const string& c)
242
{
325 andreas 243
    if (!gPageManager)
244
        return;
245
 
326 andreas 246
    gLastCommand = c;
325 andreas 247
    int channel = TConfig::getChannel();
248
    int system = TConfig::getSystem();
249
 
334 andreas 250
    string bef = c;
251
 
252
    if (startsWith(bef, "PUSH-"))
253
    {
254
        amx::ANET_SEND scmd;
255
        size_t pos = bef.find("-");
256
        string bt = bef.substr(pos + 1);
257
        vector<string> parts = StrSplit(bt, ",");
258
 
259
        if (parts.size() < 2)
260
        {
261
            MSG_ERROR("Command PUSH has syntax: PUSH-<x>,<y>!");
262
            return;
263
        }
264
 
265
        mX = atoi(parts[0].c_str());
266
        mY = atoi(parts[1].c_str());
267
        mPressed = true;
268
 
269
        if (gPageManager)
270
            gPageManager->mouseEvent(mX, mY, mPressed);
271
    }
272
    else if (startsWith(bef, "RELEASE-"))
273
    {
274
        amx::ANET_SEND scmd;
275
        size_t pos = bef.find("-");
276
        string bt = bef.substr(pos + 1);
277
        vector<string> parts = StrSplit(bt, ",");
278
 
279
        if (parts.size() < 2)
280
        {
281
            MSG_ERROR("Command RELEASE has syntax: RELEASE-<x>,<y>!");
282
            return;
283
        }
284
 
285
        mX = atoi(parts[0].c_str());
286
        mY = atoi(parts[1].c_str());
287
        mPressed = false;
288
 
289
        if (gPageManager)
290
            gPageManager->mouseEvent(mX, mY, mPressed);
291
    }
292
    else
293
    {
294
        amx::ANET_COMMAND cmd;
295
        cmd.MC = 0x000c;
296
        cmd.device1 = channel;
297
        cmd.port1 = port;
298
        cmd.system = system;
299
        cmd.data.message_string.system = system;
300
        cmd.data.message_string.device = channel;
301
        cmd.data.message_string.port = port;    // Must be the address port of button
302
        cmd.data.message_string.type = DTSZ_STRING;    // Char string
303
        cmd.data.message_string.length = min(c.length(), sizeof(cmd.data.message_string.content));
304
        memset(cmd.data.message_string.content, 0, sizeof(cmd.data.message_string.content));
305
        memcpy ((char *)cmd.data.message_string.content, c.c_str(), cmd.data.message_string.length);
306
        gPageManager->doCommand(cmd);
307
    }
325 andreas 308
}
326 andreas 309
 
334 andreas 310
void _TestMode::setMouseClick(int x, int y, bool pressed)
311
{
312
    DECL_TRACER("_TestMode::setMouseClick(int x, int y, bool pressed)");
313
 
314
    if (mX == 0 && mY == 0 && mPressed == false)
315
        return;
316
 
317
    if (mX == x && mY == y && mPressed == pressed)
318
    {
319
        __success = true;
320
        _test_screen = true;
321
        __done = true;
322
        mX = mY = 0;
323
        mPressed = false;
324
    }
325
}
326
 
327 andreas 327
void _TestMode::testSuccess(_TESTCMD& tc)
326 andreas 328
{
329
    ulong total = 0;
334 andreas 330
    bool ready = tc.compare ? (__done && _test_screen) : __done;
326 andreas 331
 
334 andreas 332
    while (!ready && total < TIMEOUT)
326 andreas 333
    {
334
        usleep(DELAY);
335
        total += DELAY;
334 andreas 336
        ready = tc.compare ? (__done && _test_screen) : __done;
326 andreas 337
    }
338
 
339
    if (total >= TIMEOUT)
340
    {
341
        MSG_WARNING("Command \"" << gLastCommand << "\" timed out!");
342
#ifdef __ANDROID__
328 andreas 343
        __android_log_print(ANDROID_LOG_INFO, "tpanel", "Command \"%s\" timed out!", gLastCommand.c_str());
326 andreas 344
#else
345
        cout << "Command \"" << gLastCommand << "\" timed out!" << endl;
346
#endif
334 andreas 347
        __success = false;
348
        tc.compare = false;
326 andreas 349
    }
350
 
334 andreas 351
    if (tc.compare && tc.result == mVerify)
327 andreas 352
    {
334 andreas 353
        MSG_WARNING("The result \"" << mVerify << "\" is equal to \"" << tc.result << "\"!");
354
        __success = tc.reverse ? false : true;
327 andreas 355
    }
356
    else if (tc.compare)
357
    {
334 andreas 358
        MSG_WARNING("The result \"" << mVerify << "\" does not match the expected result of \"" << tc.result << "\"!");
359
        __success = tc.reverse ? true : false;
327 andreas 360
    }
361
 
326 andreas 362
    if (__success)
363
    {
364
        MSG_INFO("SUCCESS (" << gLastCommand << ")");
365
#ifdef __ANDROID__
334 andreas 366
        __android_log_print(ANDROID_LOG_INFO, "tpanel", "SUCCESS");
326 andreas 367
#else
334 andreas 368
        cout << "   SUCCESS" << endl;
326 andreas 369
#endif
370
    }
371
    else
372
    {
373
        MSG_ERROR("FAILED (" << gLastCommand << ")");
374
#ifdef __ANDROID__
333 andreas 375
        __android_log_print(ANDROID_LOG_ERROR, "tpanel", "FAILED (%s): Result: %s --> Expected: %s", gLastCommand.c_str(), verify, tc.result);
326 andreas 376
#else
334 andreas 377
        cout << "   FAILED (" << gLastCommand << "): Result: " << mVerify << " --> Expected: " << tc.result << endl;
326 andreas 378
#endif
379
    }
380
 
381
    __success = false;
334 andreas 382
    _test_screen = false;
326 andreas 383
    __done = false;
384
}
385
 
386
void _TestMode::inform(const string& msg)
387
{
388
    MSG_INFO(msg);
389
#ifdef __ANDROID__
390
    __android_log_print(ANDROID_LOG_ERROR, "tpanel", "%s", msg.c_str());
391
#else
392
    cout << msg << endl;
393
#endif
394
}