1199 lines · 47.8 KB · C++
RawDownload
////  TestRailInterface.cpp////  Created by Nissim Hadar on 6 Jul 2018.//  Copyright 2013 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "TestRailInterface.h"#include "TestCreator.h" #include <quazip/quazip.h>#include <quazip/JlCompress.h> #include <QDateTime>#include <QFile>#include <QHostInfo>#include <QMessageBox>#include <QTextStream> TestRailInterface::TestRailInterface() : _pythonInterface(NULL) {    _testRailTestCasesSelectorWindow.setURL("https://highfidelity.testrail.net");    _testRailTestCasesSelectorWindow.setUser("@highfidelity.io");     _testRailTestCasesSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID);     _testRailTestCasesSelectorWindow.setSuiteID(INTERFACE_SUITE_ID);     _testRailRunSelectorWindow.setURL("https://highfidelity.testrail.net");    _testRailRunSelectorWindow.setUser("@highfidelity.io");     _testRailRunSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID);     _testRailRunSelectorWindow.setSuiteID(INTERFACE_SUITE_ID);     _testRailResultsSelectorWindow.setURL("https://highfidelity.testrail.net");    _testRailResultsSelectorWindow.setUser("@highfidelity.io");     _testRailResultsSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID);     _testRailResultsSelectorWindow.setSuiteID(INTERFACE_SUITE_ID);     _pythonInterface = new PythonInterface();    _pythonCommand = _pythonInterface->getPythonCommand();} QString TestRailInterface::getObject(const QString& path) {    return path.right(path.length() - path.lastIndexOf("/") - 1);} // Creates the testrail.py script// This is the file linked to from http://docs.gurock.com/testrail-api2/bindings-pythonvoid TestRailInterface::createTestRailDotPyScript() {    QFile file(_outputDirectory + "/testrail.py");    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {        QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),                              "Could not create 'testrail.py'");        exit(-1);    }     QTextStream stream(&file);     stream << "#\n";    stream << "# TestRail API binding for Python 3.x (API v2, available since \n";    stream << "# TestRail 3.0)\n";    stream << "#\n";    stream << "# Learn more:\n";    stream << "#\n";    stream << "# http://docs.gurock.com/testrail-api2/start\n";    stream << "# http://docs.gurock.com/testrail-api2/accessing\n";    stream << "#\n";    stream << "# Copyright Gurock Software GmbH. See license.md for details.\n";    stream << "#\n";    stream << "\n";    stream << "import urllib.request, urllib.error\n";    stream << "import json, base64\n";    stream << "\n";    stream << "class APIClient:\n";    stream << "\tdef __init__(self, base_url):\n";    stream << "\t\tself.user = ''\n";    stream << "\t\tself.password = ''\n";    stream << "\t\tif not base_url.endswith('/'):\n";    stream << "\t\t\tbase_url += '/'\n";    stream << "\t\tself.__url = base_url + 'index.php?/api/v2/'\n";    stream << "\n";    stream << "\t#\n";    stream << "\t# Send Get\n";    stream << "\t#\n";    stream << "\t# Issues a GET request (read) against the API and returns the result\n";    stream << "\t# (as Python dict).\n";    stream << "\t#\n";    stream << "\t# Arguments:\n";    stream << "\t#\n";    stream << "\t# uri                 The API method to call including parameters\n";    stream << "\t#                     (e.g. get_case/1)\n";    stream << "\t#\n";    stream << "\tdef send_get(self, uri):\n";    stream << "\t\treturn self.__send_request('GET', uri, None)\n";    stream << "\n";    stream << "\t#\n";    stream << "\t# Send POST\n";    stream << "\t#\n";    stream << "\t# Issues a POST request (write) against the API and returns the result\n";    stream << "\t# (as Python dict).\n";    stream << "\t#\n";    stream << "\t# Arguments:\n";    stream << "\t#\n";    stream << "\t# uri                 The API method to call including parameters\n";    stream << "\t#                     (e.g. add_case/1)\n";    stream << "\t# data                The data to submit as part of the request (as\n";    stream << "\t#                     Python dict, strings must be UTF-8 encoded)\n";    stream << "\t#\n";    stream << "\tdef send_post(self, uri, data):\n";    stream << "\t\treturn self.__send_request('POST', uri, data)\n";    stream << "\n";    stream << "\tdef __send_request(self, method, uri, data):\n";    stream << "\t\turl = self.__url + uri\n";    stream << "\t\trequest = urllib.request.Request(url)\n";    stream << "\t\tif (method == 'POST'):\n";    stream << "\t\t\trequest.data = bytes(json.dumps(data), 'utf-8')\n";    stream << "\t\tauth = str(\n";    stream << "\t\t\tbase64.b64encode(\n";    stream << "\t\t\t\tbytes('%s:%s' % (self.user, self.password), 'utf-8')\n";    stream << "\t\t\t),\n";    stream << "\t\t\t'ascii'\n";    stream << "\t\t).strip()\n";    stream << "\t\trequest.add_header('Authorization', 'Basic %s' % auth)\n";    stream << "\t\trequest.add_header('Content-Type', 'application/json')\n";    stream << "\n";    stream << "\t\te = None\n";    stream << "\t\ttry:\n";    stream << "\t\t\tresponse = urllib.request.urlopen(request).read()\n";    stream << "\t\texcept urllib.error.HTTPError as ex:\n";    stream << "\t\t\tresponse = ex.read()\n";    stream << "\t\t\te = ex\n";    stream << "\n";    stream << "\t\tif response:\n";    stream << "\t\t\tresult = json.loads(response.decode())\n";    stream << "\t\telse:\n";    stream << "\t\t\tresult = {}\n";    stream << "\n";    stream << "\t\tif e != None:\n";    stream << "\t\t\tif result and 'error' in result:\n";    stream << "\t\t\t\terror = '\"' + result['error'] + '\"'\n";    stream << "\t\t\telse:\n";    stream << "\t\t\t\terror = 'No additional error message received'\n";    stream << "\t\t\traise APIError('TestRail API returned HTTP %s (%s)' % \n";    stream << "\t\t\t\t(e.code, error))\n";    stream << "\n";    stream << "\t\treturn result\n";    stream << "\n";    stream << "class APIError(Exception):\n";    stream << "\tpass\n";     file.close();} // Creates a Stack classvoid TestRailInterface::createStackDotPyScript() {    QString filename = _outputDirectory + "/stack.py";    if (QFile::exists(filename)) {        QFile::remove(filename);    }    QFile file(filename);     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {        QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),                              "Could not create 'stack.py'");        exit(-1);    }     QTextStream stream(&file);     stream << "class Stack:\n";     stream << "\tdef __init__(self):\n";    stream << "\t\tself.items = []\n";    stream << "\n";     stream << "\tdef is_empty(self):\n";    stream << "\t\treturn self.items == []\n";    stream << "\n";     stream << "\tdef push(self, item):\n";    stream << "\t\tself.items.append(item)\n";    stream << "\n";     stream << "\tdef pop(self):\n";    stream << "\t\treturn self.items.pop()\n";    stream << "\n";     stream << "\tdef peek(self):\n";    stream << "\t\treturn self.items[len(self.items)-1]\n";    stream << "\n";     stream << "\tdef size(self):\n";    stream << "\t\treturn len(self.items)\n";    stream << "\n";     file.close();} bool TestRailInterface::requestTestRailTestCasesDataFromUser() {    // Make sure correct fields are enabled before calling    _testRailTestCasesSelectorWindow.reset();    _testRailTestCasesSelectorWindow.exec();     if (_testRailTestCasesSelectorWindow.getUserCancelled()) {        return false;    }     _url = _testRailTestCasesSelectorWindow.getURL() + "/";    _user = _testRailTestCasesSelectorWindow.getUser();    _password = _testRailTestCasesSelectorWindow.getPassword();     _projectID = QString::number(_testRailTestCasesSelectorWindow.getProjectID());    _suiteID = QString::number(_testRailTestCasesSelectorWindow.getSuiteID());     return true;} bool TestRailInterface::requestTestRailRunDataFromUser() {    _testRailRunSelectorWindow.reset();    _testRailRunSelectorWindow.exec();     if (_testRailRunSelectorWindow.getUserCancelled()) {        return false;    }     _url = _testRailRunSelectorWindow.getURL() + "/";    _user = _testRailRunSelectorWindow.getUser();    _password = _testRailRunSelectorWindow.getPassword();     _projectID = QString::number(_testRailRunSelectorWindow.getProjectID());    _suiteID = QString::number(_testRailRunSelectorWindow.getSuiteID());     return true;} bool TestRailInterface::requestTestRailResultsDataFromUser() {    _testRailResultsSelectorWindow.reset();    _testRailResultsSelectorWindow.exec();     if (_testRailResultsSelectorWindow.getUserCancelled()) {        return false;    }     _url = _testRailResultsSelectorWindow.getURL() + "/";    _user = _testRailResultsSelectorWindow.getUser();    _password = _testRailResultsSelectorWindow.getPassword();     _projectID = QString::number(_testRailResultsSelectorWindow.getProjectID());    _suiteID = QString::number(_testRailResultsSelectorWindow.getSuiteID());     return true;} bool TestRailInterface::isAValidTestDirectory(const QString& directory) {    if (TestCreator::isAValidDirectory(directory)) {        // Ignore the utils and preformance directories        if (directory.right(QString("utils").length()) == "utils" ||            directory.right(QString("performance").length()) == "performance") {            return false;        }        return true;    }     return false;} void TestRailInterface::processDirectoryPython(const QString& directory,                                               QTextStream& stream,                                               const QString& userGitHub,                                               const QString& branchGitHub) {    // Loop over all entries in directory    QDirIterator it(directory);    while (it.hasNext()) {        QString nextDirectory = it.next();         QString objectName = getObject(nextDirectory);         if (isAValidTestDirectory(nextDirectory)) {            // The name of the section is the directory at the end of the path            stream << "parent_id = parent_ids.peek()\n";            stream << "data = { 'name': '" << objectName << "', 'suite_id': " + _suiteID + ", 'parent_id': parent_id }\n";             stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n";             // Now we push the parent_id, and recursively process each directory            stream << "parent_ids.push(section['id'])\n\n";            processDirectoryPython(nextDirectory, stream, userGitHub, branchGitHub);        } else if (objectName == "test.js") {            processTestPython(nextDirectory, stream, userGitHub, branchGitHub);        }    }     // pop the parent directory before leaving    stream << "parent_ids.pop()\n\n";} // A suite of TestRail test cases contains trees.//    The nodes of the trees are sections//    The leaves are the test cases//// Each node and leaf have an ID and a parent ID.// Therefore, the tree is built top-down, using a stack to store the IDs of each node//void TestRailInterface::createAddTestCasesPythonScript(const QString& testDirectory,                                                       const QString& userGitHub,                                                       const QString& branchGitHub) {    QString filename = _outputDirectory + "/addTestCases.py";    if (QFile::exists(filename)) {        QFile::remove(filename);    }    QFile file(filename);     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {        QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),                              "Could not create 'addTestCases.py'");        exit(-1);    }     QTextStream stream(&file);     // Code to access TestRail    stream << "from testrail import *\n";    stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n";    stream << "client.user = '" << _user << "'\n";    stream << "client.password = '" << _password << "'\n\n";     stream << "from stack import *\n";    stream << "parent_ids = Stack()\n\n";     // top-level section    stream << "data = { 'name': '"           << "Test Section - " << QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm") + "', "           << "'suite_id': " + _suiteID + "}\n";     stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n";     // Now we push the parent_id, and recursively process each directory    stream << "parent_ids.push(section['id'])\n\n";    processDirectoryPython(testDirectory, stream, userGitHub, branchGitHub);     file.close();     if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created",                                        "Do you want to run the script and update TestRail?",                                        QMessageBox::Yes | QMessageBox::No).exec()    ) {        QProcess* process = new QProcess();         _busyWindow.setWindowTitle("Updating TestRail");        connect(process, &QProcess::started, this, [=, this]() { _busyWindow.exec(); });        connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));        connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,                [=, this](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); #ifdef Q_OS_WIN        QStringList parameters = QStringList() << _outputDirectory + "/addTestCases.py";        process->start(_pythonCommand, parameters);#elif defined Q_OS_MAC        QStringList parameters = QStringList() << "-c" <<  _pythonCommand + " " + _outputDirectory + "/addTestCases.py";        process->start("sh", parameters);#endif    }} void TestRailInterface::updateReleasesComboData(int exitCode, QProcess::ExitStatus exitStatus) {    // Quit if user has previously cancelled    if (_testRailTestCasesSelectorWindow.getUserCancelled()) {        return;    }     // Check if process completed successfully    if (exitStatus != QProcess::NormalExit) {        QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),                              "Could not get 'added to release' data from TestRail");        exit(-1);    }     // Create map of releases from the file created by the process    _releaseNames.clear();     QString filename = _outputDirectory + "/releases.txt";    if (!QFile::exists(filename)) {        QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),                              "Could not find releases.txt in " + _outputDirectory);        exit(-1);    }     QFile file(filename);    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {        QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),                              "Could not open " + _outputDirectory + "/releases.txt");        exit(-1);    }     QTextStream in(&file);    QString line = in.readLine();    while (!line.isNull()) {        _releaseNames << line;        line = in.readLine();    }     file.close();     // Update the combo    _testRailTestCasesSelectorWindow.updateReleasesComboBoxData(_releaseNames);     _testRailTestCasesSelectorWindow.exec();     if (_testRailTestCasesSelectorWindow.getUserCancelled()) {        return;    }     createAddTestCasesPythonScript(_testDirectory, _userGitHub, _branchGitHub);} void TestRailInterface::addRun() {    QString filename = _outputDirectory + "/addRun.py";    if (QFile::exists(filename)) {        QFile::remove(filename);    }    QFile file(filename);     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {        QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),                              "Could not create " + filename);        exit(-1);    }     QTextStream stream(&file);     // Code to access TestRail    stream << "from testrail import *\n";    stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n";    stream << "client.user = '" << _user << "'\n";    stream << "client.password = '" << _password << "'\n\n";     // A test suite is a forest.  Each node is a section and leaves are either sections or test cases    // The user has selected a root for the run    // To find the cases in this tree we need all the sections in the tree    // These are found by building a set of all relevant sections.  The first section is the selected root    // As the sections are in an ordered array we use the following snippet to find the relevant sections:    //      initialize section set with the root    //      for each section in the ordered array of sections in the suite    //          if the parent of the section is in the section set then    //              add this section to the section set    //    stream << "sections = client.send_get('get_sections/" + _projectID + "&suite_id=" + _suiteID + "')\n\n";     int sectionID = _sectionIDs[_testRailRunSelectorWindow.getSectionID()];     stream << "relevantSections = { " + QString::number(sectionID) + " }\n";    stream << "for section in sections:\n";    stream << "\tif section['parent_id'] in relevantSections:\n";    stream << "\t\trelevantSections.add(section['id'])\n\n";     // We now loop over each section in the set and collect the cases into an array    stream << "cases = []\n";    stream << "for section_id in relevantSections:\n";    stream << "\tcases = cases + client.send_get('get_cases/" + _projectID + "&suite_id=" + _suiteID + "&section_id=' + str(section_id))\n\n";     // To create a run we need an array of the relevant case ids    stream << "case_ids = []\n";    stream << "for case in cases:\n";    stream << "\tcase_ids.append(case['id'])\n\n";     // Now, we can create the run    stream << "data = { 'name': '" + _sectionNames[_testRailRunSelectorWindow.getSectionID()].replace("Section", "Run") + "[" +                  QHostInfo::localHostName() + "]" + "', 'suite_id': " + _suiteID +                  ", 'include_all': False, 'case_ids': case_ids}\n";     stream << "run = client.send_post('add_run/" + _projectID + "', data)\n";     file.close();     if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created",                                        "Do you want to run the script and update TestRail?",                                        QMessageBox::Yes | QMessageBox::No).exec()    ) {        QProcess* process = new QProcess();        _busyWindow.setWindowTitle("Updating TestRail");        connect(process, &QProcess::started, this, [=, this]() { _busyWindow.exec(); });        connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));        connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,                [=, this](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); #ifdef Q_OS_WIN        QStringList parameters = QStringList() << _outputDirectory + "/addRun.py";        process->start(_pythonCommand, parameters);#elif defined Q_OS_MAC        QStringList parameters = QStringList() << "-c" <<  _pythonCommand + " " + _outputDirectory + "/addRun.py";        process->start("sh", parameters);#endif    }}

Lines 1–500 of 1199.