Full text
Universidade do Minho Escola de Engenharia Ricardo Barros Pereira Development of a Self-diagnosis Tests System for Integration in a Cyber-physical System July, 2021
Universidade do Minho Escola de Engenharia Ricardo Barros Pereira Development of a Self-diagnosis Tests System for Integration in a Cyber-physical System Master’s Dissertation Master’s in Informatics Engineering Work supervised by Professor Doctor José Carlos Ramalho Professor Doctor Miguel Abrunhosa de Brito July, 2021
COPYRIGHT AND TERMS OF USE OF THIS WORK BY A THIRD PARTY This is academic work that can be used by third parties as long as internationally accepted rules and good practices regarding copyright and related rights are respected. Accordingly, this work may be used under the license provided below. If the user needs permission to make use of the work under conditions not provided for in the indicated licensing, they should contact the author through the RepositóriUM of Universidade do Minho. License granted to the users of this work Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en
STATEMENT OF INTEGRITY I hereby declare having conducted this academic work with integrity. I confirm that I have not used plagiarism or any form of undue use of information or falsification of results along the process leading to its elaboration. I further declare that I have fully acknowledged the Code of Ethical Conduct of the Universidade do Minho.
Acknowledgements I would like to express my thanks to some special people who were very important during this year and throughout my academic journey. First of all, I want to thank my advisors. To Professor Miguel Abrunhosa de Brito, for guiding me with excellence, for all his availability, and all the knowledge and motivation he gave me throughout this year. To Professor José Carlos Ramalho, for all the knowledge, experience and motivation that he provided me, not only as advisor of this dissertation but also as a teacher on several occasions throughout my academic journey. To Professor Pedro Rangel Henriques for his brilliant knowledge and ideas on domain-specific languages. To Centro de Computação Gráfica, for all the good work environment that it provided me and for all the help during the beginning of this work, until the pandemic situation sent us all to our homes and we had to say goodbye. To my family, for always being by my side and for helping me whenever I need to. To my closest friends, for always being present and also for the experiences we had during this journey. A special thanks to my friend Hugo Carvalho for being my great example in my academic journey and for always motivating, advising and helping me to make the right choices. To my dear girlfriend, Océane, for always being by my side, for all the support and all the patience she had with me while I was doing my dissertation. Finally, I would like to dedicate this work and my entire academic journey to my parents, António and Isabel, for the importance and significance that this achievement has for them. Special thanks to them for always supporting me and transmitting the best values. My sincere thanks to all of you! Ricardo Pereira This work is a result of the project POCI-01-0247-FEDER-040130, supported by Operational Program for Competitiveness and Internationalization (COMPETE 2020), under the PORTUGAL 2020 Partnership Agreement, through the European Regional Development Fund (ERDF). iii
Resumo Hoje, a CONTROLAR fornece para a Bosch a Intelligent Functional Test System Machine, um sistema ciber-físico desenvolvido para realizar diferentes níveis de testes funcionais em dispositivos e componentes electrónicos. A Bosch utiliza-a para testar o correto funcionamento dos auto-rádios produzidos. Durante este processo, os auto-rádios são submetidos a vários testes e o problema surge quando a máquina detecta erros em vários auto-rádios consecutivos e não é possível saber se a própria máquina está com problemas, pois não possui nenhum módulo que permita saber se está a funcionar corretamente ou não. A origem deste trabalho surge da necessidade de encontrar uma solução que resolva o problema enunciado, mas também, inovadora e com contribuições para o mundo da investigação em sistemas ciber-físicos e sistemas de testes de autodiagnóstico. A solução é integrar um sistema de autodiagnóstico na máquina que possa testar o seu funcionamento para que a Bosch possa ter certeza se o problema está na máquina ou nos auto-rádios. Como a máquina é um sistema ciber-físico, permite a integração de um sistema de software que possa gerir a execução de testes, sendo capaz de detectar falhas nas máquinas. O trabalho aqui apresentado aborda o problema criando um novo sistema de testes de autodiagnóstico que garantirá a confiabilidade e integridade do sistema ciber-físico. Em detalhe, esta dissertação começa por expôr um estudo sobre o estado da arte atual de sistemas ciber-físicos, automação de testes, metodologia de teste keyword-driven e mais alguns conceitos relacionados a linguagens específicas de domínio que serão relevantes para a solução final. São apresentadas a especificação e análise do sistema, a fim de definir bem os seus componentes. Uma nova arquitetura modular e extensível é proposta para sistemas de testes de autodiagnóstico, bem como uma arquitetura para estendê-lo e integrá-lo num sistema ciber-físico. Foi proposto um novo sistema de testes de autodiagnóstico que aplica a arquitetura proposta provando que é possível realizar o autodiagnóstico em tempo real do sistema ciber-físico e permitindo a integração de qualquer tipo de teste. Para validar o sistema, foram realizados 28 casos de teste, abrangendo todas as suas funcionalidades. Os resultados mostram que todos os casos de teste passaram e, portanto, o sistema cumpre todos os objetivos propostos. Palavras-chave: Sistemas Ciber-físicos, Auto-diagnóstico, Automação de Testes, Aplicação Web iv
Abstract Nowadays, CONTROLAR supplies with Bosch the Intelligent Functional Test System Machine, a cyberphysical system developed to perform different levels of functional tests on electronic devices and components. Bosch uses it to test the correct functioning of the produced car radios. During this process, the car radios are subjected to several tests and the problem arises when the machine detects errors in several consecutive car radios and it is not possible to know if the machine itself has any problems, as it does not have any module that allows knowing whether it’s working correctly or not. The origin of this work arises from the need to find a solution that solves the referred problem, but also, innovative and with contributions to the world of research in cyber-physical systems and self-diagnosis tests systems. The solution is to integrate a self-diagnosis system into the machine that can test its functionality so that when these car radio failures appear, Bosch can be sure whether the problem is with the machine or the car radio. As the machine is a cyber-physical system, it allows the integration of a software system to control and manage all its actions. Therefore, it is necessary to develop a system to manage the tests and their execution, being able to detect internal failures in the machines. The work presented here addresses the problem by creating a new self-diagnosis tests system that will guarantee the reliability and integrity of the cyber-physical system. In detail, this dissertation begins by exposing a study on the current state of the art of cyber-physical systems, test automation, keyword-driven test methodology and some more concepts related to domain-specific languages that will be relevant to the final solution. The specification and analysis of the system are presented, to define well its components. A new modular and extensible architecture is proposed for self-diagnosis test systems, as well as a methodology for extending and integrate it into a cyber-physical system. A new self-diagnosis tests system has been proposed that applies the proposed architecture proving that it is possible to carry out the self-diagnosis in real-time of the cyber-physical system and allowing the integration of any type of test. To validate the implementation of the system, 28 test cases were carried out to cover all its functionalities. The results show that all test cases passed and, therefore, the system meets all the proposed objectives. Keywords: Cyber-physical systems, Self-diagnosis, Test automation, Web Application v
Contents List of Figures ix List of Tables x Listings xi Acronyms xiii 1 Introduction 1 1.1 Problem ...................................... 2 1.2 Motivation and Objectives .............................. 2 1.3 Contributions .................................... 3 1.4 Dissertation Structure ................................ 4 2 State of the art 5 2.1 Cyber-physical systems ............................... 5 2.2 Test Automation .................................. 6 2.3 Keyword-Driven Testing ............................... 7 2.3.1 Current Tools ................................ 7 2.4 Domain-Specific Language ............................. 10 2.4.1 ANTLR ................................... 10 2.5 REST ........................................ 12 2.6 Discussion ..................................... 13 3 Analysis and Specification 14 3.1 Requirements .................................... 14 3.2 System structure .................................. 16 3.3 Technologies to use ................................. 17 3.3.1 Backend technology ............................ 17 3.3.2 Frontend technology ............................ 19 3.4 Discussion ..................................... 21 vi
CONTENTS 4 Architecture 22 4.1 Test Management and Configuration Architecture .................. 22 4.1.1 Keyword-Driven Testing Methodology .................... 22 4.1.2 Domain-Specific Language ......................... 23 4.1.3 Proposed Architecture ........................... 24 4.1.4 Example of Application ........................... 25 4.2 Self-diagnosis Tests System Architecture ....................... 26 4.2.1 Frontend .................................. 26 4.2.2 Backend .................................. 27 4.2.3 Proposed Architecture ........................... 28 4.3 General Architecture for Cyber-Physical System ................... 28 4.4 Discussion ..................................... 30 5 Implementation 32 5.1 Database ...................................... 32 5.2 Backend ...................................... 35 5.2.1 Models ................................... 36 5.2.2 Grammar ................................. 37 5.2.3 Controllers ................................. 40 5.2.4 Routes ................................... 44 5.3 Frontend ...................................... 47 5.3.1 Components ................................ 47 5.3.2 Obtaining API data ............................. 48 5.3.3 User Interfaces ............................... 49 5.4 Validation ...................................... 55 5.5 Discussion ..................................... 59 6 Conclusions and Future Work 60 6.1 Future Work .................................... 61 Bibliography 62 Appendices 67 A Backend code 67 A.1 Models ....................................... 69 A.2 Grammar ...................................... 71 A.3 Controllers ..................................... 77 A.4 Routes ....................................... 82 vii
ACRONYMS XML Extensible Markup Language xiv
Chapter 1 Introduction This research work is inserted in the context of a project called Test System Intelligent Machines (TSIM) and this project comes out from the union of skills and knowledge of the Consortium between CONTROLAR, University of Minho [4] and Centro de Computação Gráfica [7]. This arises from the challenge launched by the partnership between Bosch and the University of Minho through the Bosch Supplier Club Initiative Program. Nowadays, CONTROLAR is a company dedicated to the development of hardware and software for the industry, with a great vocation in the automotive electrical components industry and great know-how in industrial automation, development of functional and quality test systems for electronic devices. One of its business areas is the Test of systems and has as base the development and integration of functional test systems, monitoring data for the validation of electrical characteristics and quality tests, using data collection plates, generators, and specific equipment for the acquisition and analysis of signal and data. Within this business unit, Controlar has been working with several leading manufacturers worldwide, such as the multinational Bosch. The objective of this project is to guarantee the creation of value in CONTROLAR’s products (as a current Bosch supplier) and its adaptation to the reality of Bosch 4.0 Industry and the requirements of the automotive sector. The objective is to do this through the development of tools that make CONTROLAR’s automatic test equipment more flexible, efficient and intelligent. One of the products that CONTROLAR supplies to Bosch today is the Intelligent Functional Test System Machine [10]. This machine is a CPS and was developed to perform different levels of functional tests on electronic devices and components. Bosch uses this machine at the end of the production line to guarantee the correct functionality of the produced car radios. 1
CHAPTER 1. INTRODUCTION 1.1 Problem During the process of habitual use of this machine, the car radios are subjected to various tests and the machine approves the car radios whose functionalities worked correctly compared to the tests carried out. The problem appears when the machine detects errors in several car radios in a row, which could be an indicator that the production line would have failed in one of its segments, thus causing possible errors in all car radios in that line. Obviously, a scenario in which all the car radios come with errors means that something has failed in production, and for a company like Bosch this is unacceptable as it will cause unexpected delays and all the consequences that result. Besides, it makes it necessary to fix all car radios wich brings increased costs. When Bosch faces this problem it tends to attribute the problem to the machine and not to its products and, correctly or not, tries to find out what the problem is with the machine. Another additional problem is the fact that the machine does not have any module that allows understanding if it is working correctly or not so the only way they have to know it is by changing physical connections and internal properties of the machine construction to try to understand if something is a malfunction. But most of the time this happens, those looking for errors in the machine usually end up always causing damage to the machine itself without realizing it, as they do not know all the details of its construction and start to change many properties until they make the machine completely unusable and without finding the problem. The most likely result of these actions is the need to repair the machine or even replace it, which obviously causes financial losses for CONTROLAR and Bosch itself. 1.2 Motivation and Objectives The origin of this work arises from the need to find a solution that can solve the problem stated, but also that it is an innovative solution with contributions to the world of research in CPS and self-diagnosis tests systems. The solution is to integrate a self-diagnosis system in the machine that can test the functionality of the machine itself so that when these car radios failures appear, Bosch can be sure if the problem is really on the machine or in the production line of the car radios. As the machine is a CPS, it allows the integration of a software system that can control and manage all of its actions. Therefore, it is necessary to develop a testing system to manage the tests and their execution, being able to detect internal machine failures. But before we can develop a system, it is necessary to find the correct architecture for a testing system. This architecture should be as suitable as possible to the problem we are facing, but also as generic as possible regarding what a testing system is. The main objective of this dissertation is to develop a self-diagnosis tests system for integration in a CPS, which is capable of performing the self-diagnosis of CPS in real-time, thus guaranteeing its integrity. The steps and objectives for each phase of this work are as follows: • Analysis and specification of the system; 2
CHAPTER 1. INTRODUCTION • Develop and propose a new architecture for test management and configuration; • Develop and propose a new architecture for the self-diagnosis tests system; • Propose an architecture to integrate the self-diagnosis tests system in a CPS; • Development of a self-diagnosis tests system; • Validation of the self-diagnosis tests system; • Integration of the self-diagnosis tests system with the CPS. The work/research methodology will be as follows: • Study of the problem; • Survey of the state of the art; • Study on the technologies to be used in development; • Development of the system architecture; • Development, validation and integration of the system. 1.3 Contributions As previously mentioned, it was expected not only to find a solution to the problem presented but also that the solution found was innovative and had contributions to the current state of the art. Therefore, this dissertation presents the following contributions: • A modular and extensible architecture for self-diagnosis tests systems that combines the keyworddriven testing methodology with a domain-specific language for managing and configuring the tests of the system. This contribution is important because this architecture can be applied to any selfdiagnosis tests system without restricting any type of test, physical or logical, and can also be extended to CPS. • An architecture to extend and integrate the self-diagnosis tests system into a CPS. This contribution is important because this architecture proves the modularity of the architecture of the self-diagnosis tests system, demonstrating how we can extend it in a CPS. • A self-diagnosis tests system ready to be integrated into a CPS. This contribution allows validating the proposed architectures and the usefulness of the system developed in the self-diagnosis of CONTROLAR’s machines. As a result of this work, two research papers were produced, published and presented: 3
CHAPTER 1. INTRODUCTION •”Architecture based on keyword driven testing with domain specific language for a testing system” was published at the ”32nd IFIP International Conference on Testing Software and Systems” (ICTSS 2020) •”Development of Self-diagnosis Tests System Using a DSL for Creating New Test Suites for Integration in a Cyber-physical System” was published at the ”10th Symposium on Languages, Applications and Technologies” (SLATE 2021) 1.4 Dissertation Structure This document is organized as follows: Chapter 2analyzes the state of the art in CPS and self-diagnosis systems to understand whether strategies or architectures already exist for these two systems to be combined into one. This chapter also examines the state of the art in test automation and KDT methodology, in addition to analyzing and comparing some current tools that use this tests methodology. A few more concepts that will also be important for this work are also introduced in this chapter. Chapter 3analyzes and specifies the requirements and structure of the system and defines the technologies used for its implementation. Chapter 4explains the architecture defined for the system, where this process was divided into 3 steps, starting with the definition of an architecture for the management and configuration of the system tests, which will then be integrated into the architecture of the self-diagnosis tests system, and finally, the CPS architecture is composed, integrating all its components and the two previously defined architectures. Chapter 5describes the implementation of the system, explaining each of the components involved, and presents a validation of the system carried out through several of the test cases performed. Finally, chapter 6contains a conclusion of this work, which summarizes all the work done and also presents future work. 4
Chapter 2 State of the art In this chapter, a review of the state of the art in CPS and test automation will be presented in Sections 2.1 and 2.2, respectively. In Section 2.3, is presented the KDT Framework and some current tools that are a demonstration of its use, as well as the advantages and disadvantages of using them. In Section 2.4, a brief introduction is made to the concept of DSL and, more specifically, how to apply this concept with the Another Tool for Language Recognition (ANTLR). 2.1 Cyber-physical systems CPS are integrations of computing, network, and physical processes. Embedded computers and networks monitor and control physical processes. The economic and social potential of such systems is huge, and major investments are being made worldwide to develop the technology. CPS integrates the dynamics of physical processes with software and the network, providing abstractions and modelling, design, and analysis techniques for the integrated whole. But to realize the full potential of CPS, the main abstractions of computing need to be rethought and improved [32]. According to the state-of-the-art, the CPS provides the necessary technology to improve the realization and automation corresponding to a complex system on a large scale. Currently, CPS requires solutions that support it at the device, system, infrastructure, and application level. This is a challenge that includes an engineering approach and a fusion of communication, information, and automation technologies [22, 35,50]. To take advantage of the full potential of CPS, abstractions of computing need to be rethought and improved. But to improve the effective orchestration of software and physical processes, semantic models are needed to reflect the relevant properties to both [32]. Traditional control systems are adapted to each case, requiring a very expensive and time-consuming effort to develop, maintain, or reconfigure. The current challenge is to develop innovative, agile, and reconfigurable architectures for control systems, using emerging technologies and paradigms that can provide the answer to these requirements [34]. It is 5
CHAPTER 2. STATE OF THE ART still necessary to maintain a balance between these requirements and the notion of lightweight and safe solutions [39]. Despite the great development of CPS, the development of software for it remains a large and growing area, with a rich body of knowledge [57]. Concerning testing, the communication infrastructure is essential. Most tests focus on communicationoriented research, privacy and security of the communication infrastructure. For the tests created to be as appropriate as possible, research and investigation must focus on specific needs. The interconnection of the tests would provide an excellent opportunity to combine tests with different resources. This will provide an opportunity to reduce operating and investment costs. Therefore, tests must be designed to provide a remote interface [8]. Verification and composition testing methods must also be adapted to the CPS. Creating an automated or semi-automatic method to assess the results of system tests is a challenge in CPS testing that also deserves attention [5]. The CPS test remains a challenging field of research due to the increasing heterogeneity, scale, and complexity. While CPS bring some advances to existing testing theory and technology, there are still many limitations to the broader industrial application of CPS testing [60]. The challenge will be to automate the maximum number of tasks in this process and get the most out of CPS. 2.2 Test Automation The importance of testing automation is directly related to the quality of the final product. The execution of all functional tests before delivery guarantees the lowest incidence of errors in the post-delivery of the final product. As such, software developers/creators are required that their projects maintain a certain quality standard during all phases of development until the launch of a new product. Therefore, testing at the end of each stage no longer works in a professional environment. This is because the occurrence/discovery of unforeseen obstacles can significantly delay the development of the software. In recent years, it has been found that the software development market has increased its competitiveness, due to the modernization of the technologies involved and due to the maturity of the capacity to develop software. Thus, the range of information technology solutions, to meet the needs of consumer organizations, has increased considerably, which ends up making it difficult for users to choose when purchasing a product. In this competitive scenario, consumer organizations, when opting for software, are increasingly relying on quality criteria. One of the pillars for ensuring this quality of the software product is the testing process [6]. In the current software market, the concern for creating quality and error-free products has led companies to look for models and processes that guarantee quality to satisfy the needs of their customers. Unsuccessful projects, with expired deadlines and defective products, lead to customer dissatisfaction, high maintenance costs and compromise the company’s image. The main objective of a software test is to define the implementation of this software that meets all the specifications and expectations defined and expected by the customer, that is, the objective is to “verify” if what was specified in the requirements phase is what really was developed. When verifying that the implemented software meets all specifications and expectations defined and expected by the customer, it is also looked for errors in the software. The 6
CHAPTER 2. STATE OF THE ART software test must be seen as a part of its quality process. Test automation is not limited to just performing the tests but above all being aware of when and where the tests need to be carried out, thus leaving the test team more time to plan more effective tests with better quality accuracy instead of worrying about scheduling them. Thus, automation results in the mechanization of the entire process of monitoring and managing the needs for testing and evaluation associated with software development [17]. 2.3 Keyword-Driven Testing KDT is a type of functional automation testing methodology that is also known as table-oriented testing or action-based testing. In KDT, we use a table format, usually a spreadsheet, to define keywords or action words that represent the content of the tests in a simple way. But it also allows the use of a keyword to represent part of the test case and in this way make the creation of the test case simpler, since we can reuse the keywords and the whole process they represent in different test cases. It allows novice or non-technical users to write tests more abstractly and it has a high degree of reusability. The industrial control software has been having an enormous increase in complexity as technology has developed and requires a systematic testing approach to enable efficient and effective testing in the event of changes. KDT has been proving that it is a valuable test method to support these test requirements [60]. Recent results from other researchers have shown that the design of the KDT test is complex with several levels of abstraction and that this design favours reuse, which has the potential to reduce necessary changes during evolution [26]. Besides, keywords change at a relatively low rate, indicating that after creating a keyword, only localized and refined changes are made. However, the same results also showed that KDT techniques require tools to support keyword selection, refactoring, and test repair [18]. 2.3.1 Current Tools Here are presented some of the most well-known test automation tools that implement the keyword-driven test framework, as well as their characteristics and comparisons between them. Finally, an analysis is made of the advantages and disadvantages of using this type of tools. • Selenium: is an easy-to-use tool and is available for free under the Apache 2.0 license. It is an automated tool and is used to test existing applications on the web. This tool supports different browsers, namely the main ones (Google Chrome, Internet Explorer, Mozilla Firefox, etc.) [49]. In addition to being easy to use and being an intuitive tool, it does not require advanced technological or programming knowledge, and it also adapts to different programming languages [20]. It does not need installation since it works as an add-on to the browser. One of the disadvantages of this tool comes from the fact that it does not have its own repository for the objects, implying greater attention and care in its use [48]. 7
CHAPTER 2. STATE OF THE ART • QuickTest Professional: belonging to the Hewlett Packard quality center, it belongs to the functional testing category. It has a wider range of supported platforms since, in addition to working as web testing, it works on the operating system itself, requiring a license for its use. It has some privations since it has its own IDE being exclusive to it, just accept the VBScript language and finally, it only works in the browser associated with Microsoft, Internet Explorer [56]. For a good understanding and use of this test tool it is necessary to have a prior knowledge of basic programming. QTP has its own object repository, but it nevertheless has many limitations when it comes to interacting with a cloud [31]. It has a commercial cost, having a proprietary license. • TestComplete: is an automatic test tool that supports various types of applications from the web (Internet Explorer, Mozilla Firefox and Google Chrome), mobile and desktop to Java, .NET and WPF technologies [52]. This software monitors the most used keyword-driven operations and facilitates access to them, however for this to be possible the program must contain the Keyword test editor functionality [28]. Finally it is considered a very versatile tool as it supports a wide variety of programming languages from JavaScript, Python and Vbscript to C and C ++, however it only supports the Microsoft Windows operating system and presents a high-value commercial license [51]. • SilkTest: is a robust automation tool for testing web and corporate applications [14]. It is a functional testing tool as well as a regression testing tool that is economical and tests the aforementioned applications efficiently and intuitively. This tool saves time, tests scripts effectively and tests several platforms and also several browsers [36]. This software supports Windows only, but it nevertheless supports both the Windows-specific browser, Internet Explorer, as well as Mozilla Firefox. Unlike QTP, it supports several programming languages, from Java, 4Test, VBScript, C and VB.net. It also has a proprietary license at moderate cost. • RobotFramework: has an open-source testing structure that uses the keyword-driven approach, that is, keyword-driven test for their automation, thus becoming an automatic testing tool [16]. In addition to the keyword-driven, this program has the ability to use behaviour-driven and data-driven test, however, it does not have support from popular IDE’s which makes it difficult to debug the tests. It is also considered easy to install and use as it does not depend on extensive knowledge of high-level tests and provides several tools in several libraries that support the creation and execution of test cases [25]. It is a free tool under the Apache 2.0 license that was developed for Windows and Linux only and supports the most popular browsers, such as Internet Explorer, Mozilla Firefox and Google Chrome. • Ranorex: is also an automated tool that tests web applications, with the main disadvantage of not being free. It is the best software within those mentioned, for easy installation for non-programmers. This tool can be used to identify objects, such as automatic object synchronization, use of the Xpath expression, features not provided by Selenium, for example. Also, it offers resources for reuse [25]. It is only existing software for Microsoft Windows, supporting different browsers: Google Chrome, 8
CHAPTER 2. STATE OF THE ART Safari, Microsoft Edge, Internet Explorer and Mozilla Firefox, without any kind of free license. In addition to the support of web applications, it also offers possibilities to work in applications of the operating system, as well as in terms of mobile applications, existing for Android and iOS. Like QTP, it has its own IDE, having the advantage of not being limited to a single programming language, and here, there is the possibility to program in C, VB.Net or Python. In addition to being an easy to use tool and very intuitive in its interaction, it has the ability to create tests without the use of any type of programming and supports the execution of several tests in parallel. Finally, it presents a shareable object repository [47]. Tool Operating System Browser License Application Support Allowed Languages Cost Selenium All All Apache 2.0 Web Application Java, C#, Ruby, Python, PHP, Perl, … Free QuickTest Professional Microsoft Windows Internet Explorer Owner Web and Desktop Application VBScript Commercial (Free Trial) TestComplete Microsoft Windows All Owner Web, Mobile and Desktop Application VBScript, Java, C++, Python Commercial (High) SilkTest Microsoft Windows Internet Explorer, Firefox Owner Web Application Java, 4Test, VB, C# Commercial RobotFramework Microsoft Windows, Linux All Apache 2.0 Web and Desktop Application Java, Python, .Net, Pearl, PHP Free Ranorex Microsoft Windows All Owner Web and Desktop Application C#, VB, Python Commercial (Free trial) Table 2.1: Comparison of analyzed tools After analyzing and observing the characteristics of each of these tools, the following advantages and disadvantages are presented as a conclusion of their use: 2.3.1.1 Advantages • Fast execution of test cases; • Software testing in less time; • All manual testing problems are solved by automated testing; 9
CHAPTER 3. ANALYSIS AND SPECIFICATION –Perform system backups - The user can perform backup copies of the system at any time, being able to select and specify the data he wants to copy; –Restore system backups - The user can restore a system backup at any time, correcting possible failures or corrupted data; • User types and permissions –Industrial operator - The industrial operator is the most basic type of user in the system. He is responsible for realizing the tests to the car radios and the machine in the industrial dayto-day. This user will only have access to the so-called execution mode, in which he will only be able to execute tests and packages. In this mode, the user must not have access to any other functionality so that it is not possible to compromise the system; –Test Manager / Administrator - The test manager or administrator, as the word suggests, will be responsible for managing the system and all of its settings, in addition to any tests that the system will have available for execution. This user has access to all system resources. This user has access to all the features of the system and will be someone with more technical capabilities, so he has a more sophisticated environment, but maintaining the graphic simplicity that characterizes an industrial production environment. 3.2 System structure After elaborating the system requirements, this section aims to present an overview of the system structure. In the requirements analysis, the solution chosen for the structure of this system is a client-server architecture, based on the REST model. The client-server model aims at the division of tasks to reduce the system load. The server will offer a series of services to a specific user, that is, an Application Programming Interface (API), and will perform the tasks requested by the user and return the data. On the other hand, the client is responsible for requesting a specific service from the server, through messages. This is the most used model in the web community since it is possible to run several clients without compromising the operation of the system. The system will then be composed of the web client and the server. The web client will communicate with the server via HTTP requests and, in turn, the server will respond via HTTP responses. The server will be responsible for connecting to the database, making the necessary transactions or queries and returning the results. Figure 3.1 represents an illustration of the system structure. 16
CHAPTER 3. ANALYSIS AND SPECIFICATION Figure 3.1: System structure - REST model 3.3 Technologies to use After analyzing the requirements and the structure of the system, this section describes the chosen technologies for the system development and implementation. As we saw in the previous section, the system can be divided into two main components. The component that represents the Client-side will be called Frontend and the component that represents the Server-side will be called Backend. 3.3.1 Backend technology This component of the system will include the API Server and the database that will be used to ensure the consistency of the system data. Once it was decided that the system should be implemented as a web application, it makes sense to use a platform that works in the web language, JavaScript [45]. For this, Node.js [15] was chosen, which consists of a development platform based on Google JavaScript Engine V8, which allows the JavaScript language to be used on a web application server. The main objective of application development in Node.js is to be able to create fast and scalable applications that, at the same time, perform well with low memory consumption [54]. For that to happen, the platform is based on 3 concepts: • Non-blocking I/O; • Asynchronous event-driven programming; • Single thread. Unlike other development frameworks, Node.js does not rely on multithreading to run simultaneous processes, operating only on a single thread, using an event-based approach and non-blocking input and 17
CHAPTER 3. ANALYSIS AND SPECIFICATION output. The use of non-blocking I/O operations prevents the application from being blocked when calling any operation. This means that the server never waits for data to be returned. These types of attributes make Node.js lightweight and efficient compared to other structures that use I/O blocking operations [33]. In figure 3.2, it is possible to observe the processing of operations in Node.js: Figure 3.2: Processing single-thread operations in Node.js As we can see in figure 3.2, several I / O operations can occur in parallel and the respective callback of the event will be invoked as soon as the operation is finished. For this to happen, an event loop is required, which is a mechanism that will perform two tasks in a continuous cycle: event detection and triggering event handlers. The event loop is just a thread running within a process, so we can draw two conclusions: only one event handler will be running at the time and any event handler will run until the end, without being interrupted [1]. To maximize the potential of Node.js, we use the Express Framework, which provides a robust set of features for web applications and will make the system more flexible. This framework allows the creation of an API in a very fast and simple way, having a multitude of middleware methods at your disposal [13]. In summary, this framework helps us to maximize our API developed in Node.js. For data consistency, the MongoDB database was chosen. MongoDB is a document database, which means that it stores data in JavaScript Object Notation (JSON) type documents. As the data will always be traded as JSON documents, the use of MongoDB will maintain this consistency in a more natural, expressive and powerful way than any other model [53]. The way to ensure the best communication between the API and the database will be through Mongoose, which is an Object Data Modeling (ODM) library for MongoDB and Node.js [21]. It manages relationships between data, provides schema validation and is used to translate between objects in code and the representation of those objects in MongoDB. 18
CHAPTER 3. ANALYSIS AND SPECIFICATION In figure 3.3, we can see this mapping of objects between Node.js and MongoDB through Mongoose. Figure 3.3: Object Mapping between Node.js and MongoDB managed via Mongoose 3.3.2 Frontend technology For choosing the Frontend technology, obviously, there are many options, many of which are based on JavaScript and all are intended to assist in the process of building user interfaces. For this system, the tool chosen was React.js [23] due to its ability to interact with applications developed with the technologies chosen for Backend [46]. In React.js it is easy to create an interactive UI because it projects simple visualizations for each application state, efficiently updates and renders the right components when their data changes. These declarative views make the code more predictable and easier to debug [3]. React.js allows the creation of encapsulated components that manage their own state and through the composition of these components, it is possible to create more complex interfaces with less complexity in the code. For the creation of components, it is still necessary to understand the basics of 5 more technologies with which we form the components in React.js: •HTML - is a web page development language and is the main markup language on the WWW. Through HTML it is possible to create web pages, which are essentially HTML documents that are interpreted by browsers [58]. Its syntax is quite simple, based on tags, as in Extensible Markup Language (XML), where each tag will represent an element of the page, as can be seen in figure 3.4. 19
CHAPTER 3. ANALYSIS AND SPECIFICATION Figure 3.4: HTML simple structure example •Cascading Style Sheets (CSS) - is a language used to describe the appearance of HTML elements [59], that is, through CSS it is possible to manipulate the various HTML elements so that the appearance of the application is as expected. It is an easy-to-use technology, simpler than similar ones, with agile design and no license to use. • JavaScript - is an interpreted and object-oriented programming language [38]. Initially, it was created to be executed only on the client-side, that is, in browsers, but nowadays it is also used on the serverside, as in Node.js. The use of this technology will be relevant both on the Node.js server-side and on the React.js client-side, as it is through it that the logic of the components will be developed. •JSON - is a format for exchanging data. Its structure is based on the key-value relationship, that is, for each value represented, a key is assigned [27]. This format is easily interpreted by humans and machines. In this case, this technology will be particularly important since it will be used to exchange data both between the server and the client and between the server and the database. •HTTP - is a communication protocol that aims to exchange data between the client and the server. In this protocol, the React.js server sends requests, HTTP Requests, to the Node.js server, which after executing the tasks, responds with HTTP responses with the data. Since the component’s logic is written in JavaScript instead of models, we can easily pass data through the application and maintain the state outside the DOM [24]. The React virtual DOM allows the implementation of some intelligent alternative solutions that guarantee the quick rendering of the components, which is necessary since the data has to be presented immediately and effectively. In these situations, React finds an ideal way to update the UI and all you need to do is provide the data flow through the API [40]. Basically, the React virtual DOM acts as an intermediate step whenever there are changes in the web page and allows the renderings to be faster and more efficient, making the pages highly dynamic. We can see the difference between Real DOM and React Virtual DOM in figure 3.5. 20
CHAPTER 3. ANALYSIS AND SPECIFICATION Figure 3.5: Real DOM and React Virtual DOM 3.4 Discussion As mentioned, the system analysis and specification were carried out in this chapter. The system requirements have been divided into three parts to allow a better interpretation in the development of the system. A basic system structure was defined, separating it into two, the API Server, responsible for accessing the data and implementing the logic, and the Client, responsible for the user interface. Finally, the technologies chosen for the development of the system were presented and explained. For the server-side, MongoDB, Express and Node.js were chosen and for the client-side, React.js was chosen. These technologies, due to their recurring use together in the web community, are also known as MongoDB, Express, React, Node (MERN) Stack. Having all the objectives well defined and the necessary theoretical knowledge about the structure and technologies to be used in the construction of the system, in the next chapter the final abstraction of the entire system will be presented in the form of general and detailed architecture for it. 21
Chapter 4 Architecture In this chapter, the general architecture of the CPS will be presented. However, as this architecture encompasses several diversified components, its modelling was divided into three phases. In the first phase, the part of the architecture that concerns the management and configuration of the system tests is explained. The architecture of the system to be developed is explained below. In the end, the general architecture of the CPS is presented. 4.1 Test Management and Configuration Architecture The architecture that we propose in this section aims to automate and facilitate the process of creating new tests. In this architecture, we use the KDT methodology in conjunction with a DSL. To fully understand the architecture and how its components interconnect, it will be explained first how KDT and DSL are applied and only later how they are integrated into the same architecture. 4.1.1 Keyword-Driven Testing Methodology KDT will be used to abstract low-level code scripts, associating each script with a keyword that will represent it most descriptively and explicitly possible. In this way, the user does not need to know the details of the script implementation, but only what it does. We will also associate each keyword with metadata related to the corresponding test, which will be stored in a database. Figure 4.1 represents the approach taken in using KDT. The names given to the tests in the figure are only fictitious names to show that the names given to the keywords must be as descriptive as possible. 22
CHAPTER 4. ARCHITECTURE Figure 4.1: KDT Approach In Figure 4.1, we see a stack of scripts that represent the test scripts that already exist in the system, in this case, primitive tests that focus only on testing a feature or set of features, as long as they can be well-identified only by a word that can serve as a keyword. We see also the representation of a database that will be where all the information and metadata about the tests existing in the system will be stored, that is, the same ones that are represented in the stack of scripts. Connected to the database and the stack of scripts, we see a table with keywords in which each keyword represents all the information related to a test. This table is the most relevant element in the figure because it is where we can relate all the information from the stack of scripts and the database, and this is done with just one word that concedes testers with little programming knowledge to interpret what each test does or means. Finally, we have the link between the Tester and the keywords table that demonstrates the Tester will only have access to the keywords, without needing to know any details of implementation. 4.1.2 Domain-Specific Language This use of KDT alone does not bring great advantages, as we still need someone to design a test execution flow according to its purpose. This is where the importance of DSL comes in, as it allows to define a friendly language for testers, without the need for very sophisticated programming knowledge. The proposed language is extremely simple but allows the creation of new scripts with new execution flows and logical rules applied. This is achieved only using the keywords defined by the KDT and some terminal symbols defined in the DSL. Table 4.1 shows the symbols of the defined DSL terminal and what they represent. 23
CHAPTER 4. ARCHITECTURE Symbol Description keyword Catches the keywords in the script -> Catches the ”next”symbol, which means that after that symbol the next block to be executed arrives ( Catches the opening parenthesis ) Catches the closing parenthesis ? Catches the conditional expressions from the script : Catches the next block of code to be executed when a condition is false & Catches the logical operator that means intersection | Catches the logical operator that means union ; Catches the end of the script Table 4.1: DSL Symbols Description 4.1.3 Proposed Architecture To achieve the full potential of the integration between KDT and DSL, a final abstraction of all these processes is necessary. Figure 4.2 presents the architecture that guarantees to abstract the entire complex process of creating new tests for the system, thus giving the possibility to users less endowed with programming knowledge to be able to build new tests. Figure 4.2: Proposed architecture for KDT with DSL The two tables illustrated in Figure 4.2,Keywords and Symbols represent the elements that can be used to form new test scripts. The elements present in the Keywords table are the keywords that correspond to the tests defined and available in the system to be used in the creation of new tests. It is 24
CHAPTER 4. ARCHITECTURE also possible to verify the connection between the existing tests programmed in lower-level languages, such as C++, with the Keywords table. The elements in the Symbols table contain the terminal symbols of the defined DSL, that is, the only symbols recognized by the DSL. These allow giving logic and organization to the new tests of the system. Therefore, it is possible for the Tester, with the elements available in these two tables, to write the new test script and this is what is represented with the connections between the Write Script element and the tables. As soon as a new test script is written, the DSL will analyze it, using a Lexer and Parser, and verify that it is syntactically and lexically well written. This step is represented in the Compile connection. If the script complies with the defined rules, the DSL will compile that script and generate the code for a new test. But, for that, it needs to have access to the code of the tests that were used through the keywords and that is what is represented with the connection Get Tests. At the end of this process, the DSL will be able to generate the code for a new test. This is represented in the Generate Code link. From that moment, the new test is available for execution in the system. 4.1.4 Example of Application In this section, a complete example of creating a new test with this architecture is presented to demonstrate its simplicity and efficiency. In this example, it is assumed that the scope of the tests will be the same as shown in the Keywords table in figure 4.2 and the symbols that we can use are those shown in Table 4.1. The first step is to compose the script with the keywords and available symbols. In this example, we will use the following script: ( Connect & Open ) ? Read -> Write -> Close : Disconnect ; Here the scripts corresponding to the keywords Connect and Open will be executed and if both return a true value the execution will follow to the block just after ?. If any of the scripts return a false value, the next block of execution will be the one after the :symbol. The block after ?will execute the three scripts corresponding to the keywords Read,Write and Close sequentially in the order they are specified in the script. The block after :will execute only the script corresponding to the keyword Disconnect. Once the script is written, it will be analyzed by Lexer that will verify that all the elements that are in the script are part of the language. In this case, all symbols will be recognized successfully and then it is the time for Parser to continue with his analysis and check that all the rules of phrase formation are respected. After these checks, if the script is written correctly, it will be compiled and generated the new source code for the new test script. The source code of the new test will be based on the source code of the tests that were used with the keywords but adding the logic applied with the symbols used in the script. 25
Chapter 5 Implementation This dissertation aims to create a self-diagnosis tests system that will be an integrated application in CONTROLAR’s cyber-physical machines that will allow its self-diagnosis in real-time. The proposed architecture for the self-diagnosis tests system allows the management, configuration and execution of system tests, presenting a modular and extensible model that allows exploring different levels of tests to be performed on the devices under test. This chapter describes the implementation of the system and its validation. Thus, Section 5.1 explains each collection of data maintained in our database. Section 5.2 describes the Backend tier where the system logic is, including the management of the system data and the configuration and execution of the tests. Section 5.3 describes the Frontend tier that contains the user interface and the different features available for each type of user. Section 5.4 presents the results obtained from the validation performed to ensure the correct functioning of the system. Finally, section 5.5 discusses the implementation of the system and the results that have emerged from it. 5.1 Database For the database, MongoDB was used, which is a document database, that is, it stores the data in the form of JSON documents. According to the data that the system needs, 5 collections of data have been identified to be stored in the database: Configurations, Tests, Packages, Reports and Schedules. Each of these collections contains specific attributes and will be explained in detail. The configuration collection contains attributes about some configurations that may differ from machine to machine and are necessary to ensure the correct functioning of the system. The attributes of this collection are specified and explained in table 5.1: 32
CHAPTER 5. IMPLEMENTATION Attribute Description Value Type _id This attribute is the unique id of the system configurations String dbBinDir This attribute contains the MongoDB bin directory, which contains the executables needed to perform exports and imports to the database String appDir This attribute contains the directory where the system folder is located String backupDir This attribute contains the directory to which system backups will be exported and imported String userConfigurationID This attribute contains the id that allows access to the system configurations String Table 5.1: Attributes of configurations collection The tests collection stores all metadata for the system’s primitive tests. This metadata is provided by those who create and make the primitive tests available, so they are only imported into the system database and updated whenever there are changes. The attributes of this collection are specified and explained in table 5.2: Attribute Description Value Type _id This attribute is the unique id of each primitive test in the system String active This attribute has a true or false value, depending on whether the test is active, that is, available for execution, or if it is not Boolean id This attribute is the test id provided by the team that develops the primitive tests and will only be used in the system to call the test execution String module This attribute contains the name of the driver that must be executed to call the test execution String name This attribute contains the name of the test String description This attribute contains a description of the test String defaultParam This attribute contains the parameter that, by default, must be used when performing the test String Table 5.2: Attributes of tests collection The packages collection stores all metadata for the new test suites that are created in the system from the primitive tests. The attributes of this collection are specified and explained in table 5.3: 33
CHAPTER 5. IMPLEMENTATION Attribute Description Value Type _id This attribute is the unique id of each test suite in the system String active This attribute has a true or false value, depending on whether the test suite is active, that is, available for execution or not Boolean name This attribute contains the name of the test suite String description This attribute contains a description of the test suite String code This attribute contains the test suite code script developed by the system test manager. This code is developed according to the rules of the language specified in the system String path This attribute contains the name of the executable file generated to run the test suite String tests This attribute contains the list of primitive test ids (_id) that are used in the test suite. They work as references to the primitive tests. Array Table 5.3: Attributes of packages collection The reports collection stores all reports of execution of primitive tests or test packages in the system. The attributes of this collection are specified and explained in table 5.4: Attribute Description Value Type _id This attribute is the unique id of each report in the system String id_user This attribute is the id of the user who performed the execution String date This attribute contains the execution date String results This attribute contains the list of results of all tests performed, each with its own attributes Array results id_test This attribute is the unique id (_id) of the primitive test that was performed String Array module This attribute contains the name of the driver that have been executed to call the test String name This attribute contains the name of the test performed String result This attribute contains the test result (”success”, ”inconclusive”, ”fail”) String message This attribute contains the message sent about running the test String runtime This attribute contains the test execution time (ms) Number resultValue This attribute contains the return value of the test performed String Table 5.4: Attributes of reports collection 34
CHAPTER 5. IMPLEMENTATION The schedules collection stores all primitive test executions or test suite executions scheduled for a specific time by the user. The attributes of this collection are specified and explained in table 5.5: Attribute Description Value Type _id This attribute is the unique id of each schedule in the system String active This attribute has a true or false value, depending on whether the schedule is enabled to execute or not Boolean hour This attribute contains the time when the schedule is to be executed String tests This attribute contains the list of ids (_id) of all primitive tests that must be performed Array packages This attribute contains the list of ids (_id) of all test suites that must be performed Array Table 5.5: Attributes of schedules collection After specifying the data to be saved in each collection of the system’s database, the next section will explain how the system interacts with the database, through queries, to obtain the data for its operation. 5.2 Backend The Backend is the system tier responsible for managing the database and making the data available to Frontend. Therefore, framed in the Model-View-Controller (MVC) architecture, it is the Controller of the system and establishes the connection between the database and the user interfaces, thus guaranteeing the integrity of the data, not allowing other components to access or change them. The technology used to develop this server was Node.js combined with Framework Express. This server is organized so that there is a division of the code according to its function, that is, instead of all the code being in one file, it was divided into different files and directories according to its purpose on the server. This will allow the reuse and modularity of the developed code, which will also facilitate its maintenance and understanding in the future. Thus, the server structure is as follows: • Models: Here are the models that correspond to the collections saved in the database. Each model contains the attributes corresponding to its collection and performs validations related to data types to ensure that wrong data types are not inserted into the database; • Controllers: Here are the files responsible for performing all system operations, such as database queries, executing primitive tests and test suites, and creating new test suites using the DSL defined; • Grammar: Corresponds to the DSL developed for the system, where is the grammar, composed by a Lexer and a Parser, and the Visitor that generates the code for the new test suites; 35
CHAPTER 5. IMPLEMENTATION • Routes: Here is the file that routes the requests, from the client, that is, from the user interfaces to the controllers, according to the Uniform Resource Locator (URL) request. As soon as the requested operations are completed, sends the requested data to the client; • app.js: The server is activated here, through the imported Express module. From the moment it is activated, it can start receiving requests from the Client. This file can be seen in Listing A.1. Each of these elements mentioned above, has a fundamental role in the Server’s logic, so each of them will be explained in the next subsections individually. 5.2.1 Models The models represent, as mentioned, the collections stored in the database and here each model must then represent its collection and validate the data types of its attributes before transactions are made with the database. In these models, the module ”mongoose”is imported, which is an object data modelling that will allow connection to the database in an asynchronous environment and will send and receive data in JSON format, which will facilitate the use of the data in the system. Listing 5.1, shown below, serves as an example for the structure of the model files, choosing to demonstrate the model of the reports collection as it is the most complex and realizing this, all the others will be of the same or lesser level of complexity. Listing 5.1: Report Model 1const mongoose = require(”mongoose”); 2 3const testsSchema = new mongoose.Schema( 4{ 5id_test: { type: mongoose.Schema.Types.ObjectId, required: true }, 6module: { type: String, required: true }, 7name: { type: String, required: true }, 8result: { type: String, required: true }, 9message: { type: String, required: false }, 10 runtime: { type: Number, required: true }, 11 resultValue: { type: String, required: false }, 12 }, 13 { versionKey: false } 14 ); 15 16 const reportSchema = new mongoose.Schema( 17 { 18 id_user: { type: String, required: true }, 19 date: { type: String, required: true }, 20 results: [testsSchema], 21 }, 36
CHAPTER 5. IMPLEMENTATION 22 { versionKey: false } 23 ); 24 25 module.exports = mongoose.model(”report”, reportSchema); We can see in line 1 of Listing 5.1 the import of the ”mongoose”module. Next, between lines 3 and 14, inclusive, we see the model in the form of an object that represents the structure of a test result with its attributes and data types. This object serves as an auxiliary structure, in this case, to be introduced in the report model. Between lines 16 and 23, inclusive, we see the report model, also in the form of an object with its attributes and data types. In line 20, where the attribute ”results”is specified, which is a list of objects, which in this case are objects with the structure specified above for the results of each test. Finally, in line 25 we see the export of the created model, making it available for use by other files, in this case, it will be used by the Controller who will be responsible for the reports collection operations. The remaining models were also developed according to the existing collections and follow the same format, but applying their particularities according to the attributes it contains. All of them can be seen in Appendix A.1. 5.2.2 Grammar The DSL developed in this dissertation aims to enable the creation of new test suites, from the primitive tests available in the system, with rules and logic applied. This will allow the test suites to be optimized to execute in the shortest possible time and may shorten certain executions whenever the suite specifies it. The language was created from the identification of terminal symbols, that is, the symbols that would be identified by Lexer. After this step, the Parser was created, where the rules of logic and sentence construction of the grammar are specified. The terminal symbols have already been identified in table 4.1. The Lexer structure is shown below in Listing 5.2: Listing 5.2: Grammar Lexer 1lexer grammar TestLexer; 2 3NEXT : '->' ; 4AND : '&' ; 5OR : '|' ; 6 7IF : '?' ; 8ELSE : ':' ; 9 10 RPAREN : ')' ; 11 LPAREN : '(' ; 12 13 END : ';' ; 37
CHAPTER 5. IMPLEMENTATION 14 15 KEYWORD : ([A-Za-z]+([/ _-][A-Za-z]+)*) 16 ; 17 18 WS 19 : [ \r\n\t] -> skip 20 ; The structure of the Lexer is quite simple, starting with its identification and then just specifying all terminal symbols that must be recognized. The way these symbols are specified is through regular expressions, that is, for each symbol the regular expression that represents it is defined, however, always taking care that this definition does not include unexpected elements and, therefore, is not ambiguous. The symbols we see in this grammar are very intuitive and this is also one of its advantages, as it will be easy for the end-user to understand, which is one of the objectives. The only symbol that gives rise to any further explanation is the KEYWORD symbol. This symbol must recognize all the names of the primitive tests introduced in the script and, therefore, its regular expression includes isolated words or also the composition of several words, thus giving the user some freedom to be more expressive in the choice of keywords since this it is also the purpose of the KDT methodology applied in the system. After defining the terminal symbols and the Lexer specification, it is time to specify the sentence construction rules with these symbols and this is done in the Parser, which is shown below in Listing 5.3: Listing 5.3: Grammar Parser 1parser grammar TestParser; 2 3options { 4tokenVocab=TestLexer; 5} 6 7test 8: statement END 9; 10 11 statement 12 : condition #Conditional 13 | seq #Sequence 14 ; 15 16 condition 17 : expr IF statement ELSE statement #IfElse 18 | expr IF statement #If 19 ; 20 21 seq 22 : KEYWORD (NEXT statement)* 38
CHAPTER 5. IMPLEMENTATION 23 ; 24 25 expr 26 : LPAREN KEYWORD (AND KEYWORD)* RPAREN #And 27 | LPAREN KEYWORD (OR KEYWORD)* RPAREN #Or 28 ; The Parser also starts with its identification, following the reference for the Lexer that it provides the symbols to be able to know which are the terminal symbols. After these two steps, the sentences of the grammar are specified and here there is no more than a specification of the sequences that the elements of the language can follow. We can see, for example, in the element statement two possibilities. One possible statement is the condition that represents a conditional expression and the other possibility is a seq that represents a tests sequence. The most important part of the Parser to retain is the elements that come at the end of the lines for each possibility determined at the beginning of words by a #. This allows the Visitor to know the possible paths in the parsing tree that this Parser will generate. So that this grammar can now be used by the system and generate the parsing tree that will be interpreted by the Visitor, it is still necessary to find a way to use it in the system. Since ANTLR offers the transformation of these grammars for several known programming languages, we will proceed to transform the grammar into JavaScript and include the code directly in the system. For this, it is necessary to execute the following command: $ antlr4 -Dlanguage=JavaScript Lexer.g4 Parser.g4 -no-listener -visitor In this command, we specify the Lexer and Parser to be transformed and we also specify that we do not want the generation of a Listener because, by default, it generates the Listener. Finally, we specify the generation of a Visitor because, by default, it does not generate the Visitor. After executing this command, several files will be generated, among which, the Visitor that will be the most important in the next steps, as this is where the code to be generated for the new test suites will be specified. We can see below, in Listing 5.4, an example of a Visitor function: Listing 5.4: Grammar Visitor 1TestParserVisitor.prototype.visitAnd = function (ctx) { 2this.auxOp = 0; 3for (let i = 0; i < ctx.KEYWORD().length; i++) { 4this.auxList.push(ctx.KEYWORD(i)); 5} 6return ””; 7}; The Visitor’s strategy developed is to go through the code script through the elements specified in the Parser and each element generate the corresponding code. The generated code, within the Visitor, is nothing more than a string that is incremented and filled up to the end of the parsing tree. All keywords 39
CHAPTER 5. IMPLEMENTATION are also being saved in a list so that the list and the string containing the generated script are returned at the end. The list of keywords is necessary because after generating this code it will be necessary to match the keywords with the primitive tests but this is a process already done in the packages controller. The entire Visitor code can be seen in more detail in Appendix A.9. 5.2.3 Controllers The controllers, as mentioned earlier, are responsible for performing the system operations, that is, all queries, all executions of primitive tests and test suites and the creation of new test suites. So the way the controllers are structured is similar to the models, there is a file for each model that is responsible for carrying out the operations related to that collection or model. In each controller, several operations are available according to what is necessary for each one, but what is common to all are the Create, Read, Update, and Delete (CRUD) operations. In addition to these operations, there are even more that are particular to only a few models, such as the execution of primitive tests which is an operation developed only on the tests controller, the creation of new test suites that make use of the developed DSL and the execution of those same test suites which are operations developed only on the packages controller. The following operations demonstrate some examples of the CRUD operations mentioned, with different controllers: • Method to get all reports from the database, ordered by date in descending order: Listing 5.5: Read all reports operation 1module.exports.getReports = () => { 2return Report.find().sort({ date: -1 }).exec(); 3}; • Method to get information related to one report, passing the id of the report as an argument: Listing 5.6: Read one report operation 1module.exports.getReport = (idReport) => { 2return Report.findOne({ _id: idReport }).exec(); 3}; • Method to insert a schedule in the schedules collection, passing as an argument an object with the attributes and values of the new schedule: Listing 5.7: Create one schedule operation 1module.exports.insertSchedule = (schedule) => { 2let s = new Schedule(schedule); 3return s.save(); 4}; 40
CHAPTER 5. IMPLEMENTATION • Method to update a test in the tests collection, passing as argument the id of the test to be updated and an object with the attributes and values of the updated test: Listing 5.8: Update one test operation 1module.exports.updateTest = (idTest, newTest) => { 2return Test.findOneAndUpdate({ _id: idTest }, newTest); 3}; • Method to update all schedules in the schedules collection, in this case, to remove a particular test from all schedules, passing the id of the test to remove as an argument. This method is generally used when a test is removed from the system and it no longer makes sense to have a scheduled execution including the respective test: Listing 5.9: Update many schedules operation 1module.exports.removeTestFromAll = (idTest) => { 2return Schedule.updateMany( 3{ }, 4{ $pull: { tests: idTest } } 5).exec(); 6}; • Method to delete a schedule from the schedules collection, passing the id of the schedule to be removed as an argument: Listing 5.10: Delete one schedule operation 1module.exports.deleteSchedule = (idSchedule) => { 2return Schedule.deleteOne({ _id: idSchedule }); 3}; The operations shown below are those mentioned different from the usual CRUD, however, given the context of the system they are fundamental to its performance: • Method to execute a primitive test, passing as arguments the directory where the electronic test drivers are kept, the id of the test to be executed and the parameters of the test. In the first phase of this method, a query is made to obtain all the information related to the test and the counting of the execution time starts. Then, the driver responsible for executing the test is executed, which executes it and returns the results. As soon as the results arrive, the run time count stops and the test run time is saved in the results. Finally, some information about the test is added to the results to be saved in the reports and the object containing the results of the test execution is returned: 41
CHAPTER 5. IMPLEMENTATION Figure 5.1: Interactions between reaction components 5.3.2 Obtaining API data Another important aspect for this part of the system to work as planned is to obtain the data that is managed by the Backend tier. For the graphical interfaces built to be as optimized as possible and quick in obtaining data, so that the user does not have to wait long to load the pages, the data must be obtained in the best way. And here the decision made was that the parent components of each page make the data requests to the API at the time of its creation. With this, what happens on the system pages is that whenever the user changes the page or enters a new page, the data is requested and loaded. This will allow the actions taken by the user on the components belonging to these pages to be carried out much more quickly, giving the user the perception that nothing has happened when real events and state changes have already occurred witch allows the page to become dynamic with speed desired. The way to obtain the data is through HTTP requests, explained previously, therefore, to make the code clearer, a file was created only for the methods of requesting data from the API. This file contains the base URL of the Data API and all methods add only the route and sub-route as needed. We can see below, in Listing 5.18, an example of a method of obtaining data by making an HTTP request to the data API: Listing 5.18: Example of request to obtain API data 1export const getTests = async () => { 2try { 3const response = await axios.get(`${url}/tests`); 4return response.data; 5}catch (error) { 6const statusCode = error.response ? error.response.status : 500; 7throw new Error(statusCode.toString()); 8} 9}; 48
CHAPTER 5. IMPLEMENTATION In this example, we can see how HTTP requests are made to the API. These requests are made through the imported module ”Axios”since the technology does not provide this functionality natively. Another important feature that we see in this example is the use of the keyword ”await”, which in this particular case makes the method wait for the results of the API. This is also one of the strong characteristics of the technologies used, as they allow to establish of asynchronous communications. All other methods that make API requests to the Frontend are visible in Appendix B.1. 5.3.3 User Interfaces Taking into account the users of the system, the division of access to the pages by each user was carried out immediately through the login on the first page, which will allow assigning a JSON Web Token (JWT) to the user and will only give him access to the appropriate functionalities. We can see the login page in figure 5.2: Figure 5.2: Login Page On this page, the user must enter his ID (provided by CONTROLAR) and enter the appropriate mode. If it is an operator it must enter the execution mode, if it is the test manager it must enter the configuration mode. Still, the system according to the ID only allows users to enter the appropriate mode. On the page available to the operator, it is only possible to execute primitive tests or test packages. The operator has, on the left side of the interface, a list with all the primitive tests of the system. To execute these tests, it must select the ones he wants, and they can execute all at once, and send them to the list on the right which is properly identified as the list of tests to be executed. After selecting the tests and passing them to the execution pipe, it just needs to press the button to execute. The system will execute the tests and, in the end, a table will be presented to the user with the results obtained. The execution interface and the results table presented to the user can be viewed below, in figures 5.3 and 5.4, respectively: 49
CHAPTER 5. IMPLEMENTATION Figure 5.3: Execution Page The way described above for the process of selecting and executing primitive tests by the user is the same for the test suites but on the right side of the page. The results in this case are also shown in the same way. Figure 5.4: Execution Results Table As for the results table presented to the user, it shows all the data provided to us from the execution drivers and also some metadata that is already added by the system for better understanding. In this case, the table data provided by the execution drivers are the message, the result value and the test result. The message is an informative attribute for the user about the occurrence of the test. The result value is the value returned by the test execution because normally the drivers perform practical functions with results in the order of the units corresponding to the test application. The important metric for the effect of the test results on the system is the result and it simply tells us whether the test passed, failed or was inconclusive. To give greater visual intuition about the test results, coloured balls were also added to the 50
CHAPTER 5. IMPLEMENTATION test result, in which green means that the test passed, yellow was inconclusive and red failed. In this way, the visualization of the results and the interpretation will be better. For the first type of user, the industrial operator, these are the actions and resources to which he has access. Note that the purpose of these interfaces is to be as simple and functional as possible so that there are no ambiguities in the user’s decision making. For the second type of user, the test manager or administrator, there will be more pages and resources accessible. Starting with the execution reports page shown in figure 5.5, this page presents a table with all the execution reports made in the system. What we see in each row of this table is the information summarized in the report, such as the id of the employee who performed the executions of that report, the date of execution, the time of execution of all tests performed and the results, that is, a relationship between the number of approved and failed or inconclusive tests. Again, here we also see the use of colours in the results to facilitate the reading and analysis of the results of the executions. Figure 5.5: Reports page Each row in the table also allows for expansion that opens an internal table, detailing the results of all tests performed in this report. This internal table follows the same format as the table shown to the user when executing the tests on the test execution page. The second page that the manager has access to is the execution schedules page, shown in figure 5.6. On this page, we see a list of schedules, in which each element of the list contains the time when it will be executed, a button to determine if is active or not and an option to delete. An informational message is detailed, stating how many hours and minutes are left to run. To edit the schedule and change its properties, just click on the desired element and a form similar to the one for creating the schedule is opened, with the difference that it is already filled in with its information. 51
CHAPTER 5. IMPLEMENTATION Figure 5.6: Schedules page In the form, the information to be filled out is the time to execute, the activation of the scheduling and the selection of the primitive tests and test suites to be executed. The form can be seen in figure 5.7. Figure 5.7: Form to add and update schedule The next page that the user has available is the page for managing and configuring new test suites for the system, which can be seen in figure 5.8. The user has on this page at his disposal the list of existing packages in the system, where he can remove or edit them. There is also a form for creating a new test suite, where the user only needs to specify the name, description and code of the new test suite, the code is written with the DSL developed in this work. In this case, the elements that can be used to write the code are the connectors below the form that are made available to the user according to the status of their script, to help the user and try to avoid errors. The other elements to include in the script are the primitive tests, and these are made available in a list next to the form where the user can even see the description to understand what the test does. To include a test in the script just needs to click on it and it 52
CHAPTER 5. IMPLEMENTATION is automatically added to the script. This way, the user does not need to write anything manually, having to select the elements he wants to add to the script. Figure 5.8: Package creation and management page The fourth page that the manager has access to is the primitive tests documentation page, and this is a purely informative but important page for those who will manage the tests and test suites for the system. This is because whoever develops the primitive tests may not be the same person who later manages the system and even who creates the test suites, so there needs to be a page where users can be informed about the primitive tests of the system. This page can be seen in figure 5.9. Figure 5.9: Documental page about available primitive tests The manager’s last page is the page he has access to and must update the system configurations whenever necessary. These configurations are necessary for the system to function, as they are used in fundamental processes all the time. As configurations there is the manager ID, which is the only one to have access to this system mode, it has the MongoDB bin directory, that is, the directory where MongoDB 53
CHAPTER 5. IMPLEMENTATION executable files are located and which allow data extractions and imports, there is the directory where the system itself is installed and finally the directory to which backups are to be exported and also where they are to be imported from. This page can be seen in figure 5.10: Figure 5.10: System configurations page and data export and import The features that this page has available to the user, in addition to the management of the system configurations, are the possibility to export the system execution reports in CSV format, which can be filtered between two dates or not, the possibility to make backup copies, be able to customize the data you want to copy and restore backup copies on the system. The backup copies will allow the system to be always aware of situations of failure or data corruption. However, all of these features require that the system configurations are properly completed and correct. Having thus presented all the pages that the system makes available, all the code developed for them can be found in Appendix B.4. 54
CHAPTER 5. IMPLEMENTATION 5.4 Validation Having already implemented the system with all the requirements that were established, several test cases were created to be carried out in the system to validate the solution and confirm the fulfilment of all the proposed objectives. The first tests were carried out on the most fundamental functionalities of the system, the execution of the tests and the automation of the update in the face of changes introduced in its supply. Several test scenarios were simulated and the system behaved as expected, passing all tests performed. We can see the tests performed and their results in table 5.10. Test Case Test Steps Test Data Expected Result Actual Results Pass/Fail Check the execution of primitive tests 1. Select tests to execute 2. Click the button to run 3. When window to confirm appear, select yes Tests Selected: am_power_on set_frequency am_seek_left A table with the test results must appear Table presented with the results Pass Check the execution of all primitive tests 1. Select all tests to execute 2. Click the button to run 3. When window to confirm appear, select yes Tests Selected: All available A table with the all test results must appear Table presented with all results Pass Check for a message when no test is selected to notify 1. Click button to execute without selecting tests None A warning message should appear notifying you that the user has not selected any tests Message appeared with the notification Pass Check if system updates the removed primitve tests 1. Go to the electronic test drivers directory 2. Remove driverA.json from directory 3. Open the system in the execution mode 4. Check if tests of driver A are available None Tests from driver A must not appear to be selected Tests are not available Pass Check if system updates the added primitve tests 1. Go to the electronic test drivers directory 2. Add driverA.json from directory 3. Open the system in the execution mode 4. Check if tests of driver A are available None Tests from driver A must appear to be selected Tests are available Pass Check the execution of a test suite 1. Select test suite to execute 2. Click the button to run Test suites Selected: Pacote AM FM A table with the test suite results must appear Table presented with the results Pass Check the execution of all test suites 1. Select all test suites to execute 2. Click the button to run 3. When window to confirm appear, select yes Test suitesSelected: All available A table with the all test suite results must appear Table presented with all results Pass Check for a message when no test suite is selected to notify 1. Click button to execute without selecting tests None A warning message should appear notifying you that the user has not selected any test suites Message appeared with the notification Pass Table 5.10: Results of test cases performed on test executions and management This table contains in each row: • Test Case - The test case is the description of the test and the functionality that will be tested, so it 55
CHAPTER 5. IMPLEMENTATION must be as descriptive and explicit as possible so that anyone who does not know the functionality of the system can understand what the test does; • Test Steps - The test steps are the steps that the tester must follow strictly to reproduce exactly the same result or attempt; • Test Data - The test data is the data that will be needed to perform the test, it may be necessary to insert in forms in the interface for example; • Expected Result - The expected result is the result that the test must achieve to meet the system requirements; • Actual Result - The actual result is the result that the test obtained after the execution; • Pass/Fail - The test, in the end, must pass or fail. It must pass if the actual result obtained from its realization is equal to the expected result and fails otherwise. After carrying out the test cases discussed above, the test cases were performed for all other features of the system, with the results tables all having the same format. We can see the results and test cases remaining in the following tables: 5.11,5.12 and 5.13. Test Case Test Steps Test Data Expected Result Actual Results Pass/Fail Check the execution reports 1. Open tab ”Relatórios” None A table with the all reports must appear Table Presented Pass Check the details of an execution report 1. Open tab ”Relatórios” 2. Choose a report and click on it None A table with the evidence of the results of the tests carried out in that report must be presented Table Presented Pass Check documentation of primitive tests 1. Open tab ”Documentação” None A table with all primitve tests metada must be presented Table Presented Pass Check the schedule of a new execution 1. Click in the button to add schedule 2. Enter time 3. Activate the option button 4. Select the primitive tests to execute 5. Select the test suites to execute 6. Click in the save button Time: 8:00 Active: True Tests Selected: power_on set_fm_frequency fm_seek_right A new schedule must be added to the system Schedule created Pass Check trying to schedule a new execution without selecting any test 1. Click in the button to add schedule 2. Enter time 3. Activate the option button 4. Click in the save button Time: 10:00 Active: True An error message must appear stating that at least one test must be selected Message appeared Pass Check the update of a schedule 1. Click in the schedule timed to 8:00 2. Change time to 9:00 3. Click in the save button Time: 9:00 The schedule must be updated Schedule updated Pass Check the removal of a schedule 1. Click in the optin button to remove in the schedule timed to 9:00 None The schedule must be removed Schedule removed Pass Table 5.11: Results of test cases performed on visualization reports, documentation of primitive tests and scheduling of executions 56
CHAPTER 5. IMPLEMENTATION Test Case Test Steps Test Data Expected Result Actual Results Pass/Fail Check the creation of a new test suite with wrong script 1. Open tab ”Gestão de Pacotes” 2. Enter package name 3. Enter package description 4. Write the script 5. Click in the save button Package Name: Novo Pacote Package Description: Pacote para demonstrar erro Package Script: InventedTest -> am_power_on -> An error alert must appear to the user saying the code is not correct Alert showned Pass Check the creation of a new test suite 1. Open tab ”Gestão de Pacotes” 2. Enter package name 3. Enter package description 4. Write the script 5. Click in the save button Package name: New Package Package description: Package for demonstrattion Package script: power_on-> am_power_on ; Test suite must be created and should appear in the list on the left side Test suite created and available Pass Check the update of a test suite 1. Open tab ”Gestão de Pacotes” 2. Click in the package named ”New Package” 3. Click in the edit button 4. Change the name 5. Click in the save button Package name: New Package to Remove Test suite must be updated Test suite was updated Pass Check the removal of a test suite 1. Open tab ”Gestão de Pacotes” 2. Click in the package named ”New Package to remove” 3. Click in the remove button None Test suite must be removed Test suite was removed Pass Check the export of reports to a CSV with dates no covered 1. Open tab ”Configurações” 2. Enter begin date on export CSV 3. Enter end date on export CSV 4. Click download button Begin Date: 25/07/2021 End Date: 01/09/2021 A CSV file must be downloaded without lines CSV file was downloaded with no lines Pass Check the export of reports to a CSV 1. Open tab ”Configurações” 2. Enter begin date on export CSV 3. Enter end date on export CSV 4. Click download button Begin Date: 01/01/2021 End Date: 01/08/2021 A CSV file must be downloaded that contains all the reports from the specified interval CSV file downloaded with the all reports from the interval Pass Table 5.12: Results of test cases performed in managing and creating test suites and exporting reports to CSV 57
BIBLIOGRAPHY [26] Jingfan Tang, Xiaohua Cao, and A. Ma. “Towards adaptive framework of keyword driven automation testing.” In: 2008 IEEE International Conference on Automation and Logistics. 2008, pp. 1631– 1636. doi: 10.1109/ICAL.2008.4636415. [27] json.org. JSON. 2021. url: https://www.json.org/json-en.html. (accessed: 30.12.2020). [28] M. Kaur and R. Kumari. “Comparative Study of Automated Testing Tools: TestComplete and QuickTest Pro.” In: International Journal of Computer Applications 24.1 (2011), pp. 1–7. doi: 10.5120/ 2918-3844. [29] T. Kosar, S. Bohra, and M. Mernik. “Domain-Specific Languages: A Systematic Mapping Study.” In: Information and Software Technology (2016). issn: 09505849. doi: 10.1016/j.infsof.2015. 11.001. [30] C. W. Krueger. “Software Reuse.” In: ACM Computing Surveys (CSUR) (1992). issn: 15577341. doi: 10.1145/130844.130856. [31] T. Lalwani. QuickTest Professional Unplugged: 2nd Edition. KnowledgeInbox, 2011. isbn: 0983675910. [32] E. A. Lee. “Cyber physical systems: Design challenges.” In: Proceedings - 11th IEEE Symposium on Object/Component/Service-Oriented Real-Time Distributed Computing, ISORC 2008. 2008. isbn: 9780769531328. doi: 10.1109/ISORC.2008.25. [33] K. Lei, Y. Ma, and Z. Tan. “Performance comparison and evaluation of web development technologies in PHP, Python and Node.js.” In: Proceedings - 17th IEEE International Conference on Computational Science and Engineering, CSE 2014, Jointly with 13th IEEE International Conference on Ubiquitous Computing and Communications, IUCC 2014, 13th International Symposium on Pervasive Systems, Algorithms, and Networks, I-SPAN 2014 and 8th International Conference on Frontier of Computer Science and Technology, FCST 2014. 2015. isbn: 9781479979813. doi: 10.1109/CSE.2014.142. [34] P. Leitão. “Agent-based distributed manufacturing control: A state-of-the-art survey.” In: Engineering Applications of Artificial Intelligence (2009). issn: 09521976. doi: 10.1016/j.engappai.2008. 09.005. [35] P. Leitão, A. W. Colombo, and S. Karnouskos. “Industrial automation based on cyber-physical systems technologies: Prototype implementations and challenges.” In: Computers in Industry (2016). issn: 01663615. doi: 10.1016/j.compind.2015.08.004. [36] T. Lima, A. Dantas, and L. Vasconcelos. “Usando o SilkTest para automatizar testes: um Relato de Experiência.” In: Icomp.Ufam.Edu.Br. 2012. [37] M. Mernik, J. Heering, and A. M. Sloane. “When and how to develop domain-specific languages.” In: ACM Computing Surveys (2005). issn: 03600300. doi: 10.1145/1118890.1118892. [38] Mozilla. JavaScript. 2021. url: https : / / developer . mozilla . org / pt - PT / docs / Web / JavaScript. (accessed: 30.12.2020). 64
BIBLIOGRAPHY [39] A. H. Ngu, M. Gutierrez, V. Metsis, S. Nepal, and Q. Z. Sheng. “IoT Middleware: A Survey on Issues and Enabling Technologies.” In: IEEE Internet of Things Journal (2017). issn: 23274662. doi: 10.1109/JIOT.2016.2615180. [40] E. Obinna. Use the React Profiler for Performance. 2018. [41] J. Palsberg and C. B. Jay. “The essence of the Visitor pattern.” In: Proceedings - International Computer Software and Applications Conference. 1998. isbn: 0818685859. doi: 10.1109/CMPSAC. 1998.716629. [42] T. J. Parr and R. W. Quong. “ANTLR: A predicated�LL(k) parser generator.” In: Software: Practice and Experience (1995). issn: 1097024X. doi: 10.1002/spe.4380250705. [43] T. Parr and K. Fisher. “LL(*): The foundation of the ANTLR parser generator.” In: Proceedings of the ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI). 2011. isbn: 9781450306638. doi: 10.1145/1993498.1993548. [44] T. Parr, S. Harwell, and K. Fisher. “Adaptive LL(*) parsing.” In: ACM SIGPLAN Notices (2014). issn: 0362-1340. doi: 10.1145/2714064.2660202. [45] Pluralsight. JavaScript. 2021. url: https://www.javascript.com/. (accessed: 29.12.2020). [46] P. Porter, S. Yang, and X. Xi. “The Design and Implementation of a RESTful IoT Service Using the MERN Stack.” In: Proceedings - 2019 IEEE 16th International Conference on Mobile Ad Hoc and Smart Systems Workshops, MASSW 2019. 2019. isbn: 9781728141213. doi: 10.1109/MASSW. 2019.00035. [47] Ranorex. Test Automation Tools. 2021. url: https://www.ranorex.com/test-automationtools/. (accessed: 20.01.2021). [48] R. A. Razak and F. R. Fahrurazi. “Agile testing with Selenium.” In: 2011 Malaysian Conference in Software Engineering. 2011, pp. 217–219. doi: 10.1109/MySEC.2011.6140672. [49] Selenium. About Selenium. url: https://www.selenium.dev/about/. (accessed: 20.01.2021). [50] S. A. Seshia, S. Hu, W. Li, and Q. Zhu. “Design Automation of Cyber-Physical Systems: Challenges, Advances, and Opportunities.” In: IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems (2017). issn: 02780070. doi: 10.1109/TCAD.2016.2633961. [51] SmartBear. TestComplete System Requirements. 2020. url: https://support.smartbear. com / testcomplete / docs / general - info / system - requirements . html. (accessed: 20.01.2021). [52] SmartBear. TestComplete Automated UI Testing Tool. 2021. url: https: //smartbear.com/ product/testcomplete/overview/. (accessed: 20.01.2021). [53] V. Subramanian and V. Subramanian. “MongoDB.” In: Pro MERN Stack. 2019. doi: 10.1007/9781-4842-4391-6_6. 65
BIBLIOGRAPHY [54] S. Tilkov and S. Vinoski. “Node.js: Using JavaScript to build high-performance network programs.” In: IEEE Internet Computing (2010). issn: 10897801. doi: 10.1109/MIC.2010.145. [55] G. Tomassetti. The ANTLR Mega Tutorial. 2021. url: https://tomassetti.me/antlr-megatutorial/. (accessed: 27.01.2021). [56] Q. Tutorial. Tutorialspoint. url: https://www.tutorialspoint.com/qtp/index.htm. (accessed: 20.01.2021). [57] V. Vyatkin. Software engineering in industrial automation: State-of-the-art review. 2013. doi: 10. 1109/TII.2013.2258165. [58] W3C. HTML5: A vocabulary and associated APIs for HTML and XHTML. 2010. url: https://www. w3.org/TR/2010/WD-html5-20100624/. (accessed: 30.12.2020). [59] W3C. Cascading Style Sheets. 2021. url: https://www.w3.org/Style/CSS/Overview.en. html. (accessed: 30.12.2020). [60] X. Zhou, X. Gou, T. Huang, and S. Yang. “Review on Testing of Cyber Physical Systems: Methods and Testbeds.” In: IEEE Access 6 (2018), pp. 52179–52194. doi: 10.1109/ACCESS.2018.2869834. 66
Appendix A Backend code This appendix contains the complete code for the Backend tier, that is, for the Server of the System. Although the examples specified and presented in the main document are more than sufficient to understand the work done, all the code has been exposed here as supporting documentation for this work. Thus, it is possible to reproduce the work done with the presented documentation and code. Listing A.1: App.js 1const createError = require(”http-errors”); 2const express = require(”express”); 3const path = require(”path”); 4const logger = require(”morgan”); 5const mongoose = require(”mongoose”); 6const cors = require(”cors”); 7 8mongoose 9.connect(”mongodb://localhost:27017/TSIM”, { 10 useNewUrlParser: true, 11 useUnifiedTopology: true, 12 }) 13 .then(() => console.log(”Mongo Ready: ” + mongoose.connection.readyState)) 14 .catch((erro) => console.log(”Mongo: erro na conexão: ” + erro)); 15 16 const apiRouter = require(”./routes/api”); 17 18 const app = express(); 19 20 // view engine setup 21 app.set(”views”, path.join(__dirname, ”views”)); 22 app.set(”view engine”, ”pug”); 67
APPENDIX A. BACKEND CODE 23 24 const corsOpts = { 25 origin: ”*”, 26 credentials: true, 27 methods: [”GET”, ”PUT”, ”POST”, ”DELETE”, ”OPTIONS”], 28 allowedHeaders: [ 29 ”Accept”, 30 ”Authorization”, 31 ”Cache-Control”, 32 ”Content-Type”, 33 ”DNT”, 34 ”If-Modified-Since”, 35 ”Keep-Alive”, 36 ”Origin”, 37 ”User-Agent”, 38 ”X-Requested-With”, 39 ”Content-Length”, 40 ], 41 }; 42 app.use(cors(corsOpts)); 43 app.use(logger(”dev”)); 44 app.use(express.json()); 45 app.use(express.urlencoded({ extended: false })); 46 app.use(express.static(path.join(__dirname, ”public”))); 47 48 app.use(”/”, apiRouter); 49 50 // catch 404 and forward to error handler 51 app.use(function (req, res, next) { 52 next(createError(404)); 53 }); 54 55 // error handler 56 app.use(function (err, req, res, next) { 57 // set locals, only providing error in development 58 res.locals.message = err.message; 59 res.locals.error = req.app.get(”env”) === ”development” ? err : {}; 60 61 // render the error page 62 res.status(err.status || 500); 63 res.render(”error”); 64 }); 65 66 module.exports = app; 68
APPENDIX A. BACKEND CODE A.1 Models Listing A.2: Configurations Model 1const mongoose = require(”mongoose”); 2 3const configurationSchema = new mongoose.Schema( 4{ 5dbBinDir: { type: String, required: true }, 6appDir: { type: String, required: false }, 7userConfigurationID: { type: String, required: false }, 8backupDir: { type: String, required: false }, 9}, 10 { versionKey: false } 11 ); 12 13 module.exports = mongoose.model(”configuration”, configurationSchema); Listing A.3: Packages Model 1const mongoose = require(”mongoose”); 2 3const packageSchema = new mongoose.Schema( 4{ 5active: { type: Boolean, default:true }, 6name: { type: String, required: true }, 7description: { type: String, required: false }, 8code: { type: String, required: true }, 9path: { type: String, required: true }, 10 tests: [{ type: mongoose.Schema.Types.ObjectId, required: true }], 11 }, 12 { versionKey: false } 13 ); 14 15 module.exports = mongoose.model(”package”, packageSchema); Listing A.4: Reports Model 1const mongoose = require(”mongoose”); 2 3const testsSchema = new mongoose.Schema( 4{ 5id_test: { type: mongoose.Schema.Types.ObjectId, required: true }, 6module: { type: String, required: true }, 7name: { type: String, required: true }, 8result: { type: String, required: true }, 9message: { type: String, required: false }, 69
APPENDIX A. BACKEND CODE 10 runtime: { type: Number, required: true }, 11 resultValue: { type: String, required: false }, 12 }, 13 { versionKey: false } 14 ); 15 16 const reportSchema = new mongoose.Schema( 17 { 18 id_user: { type: String, required: true }, 19 date: { type: String, required: true }, 20 results: [testsSchema], 21 }, 22 { versionKey: false } 23 ); 24 25 module.exports = mongoose.model(”report”, reportSchema); Listing A.5: Schedules Model 1const mongoose = require(”mongoose”); 2 3const scheduleSchema = new mongoose.Schema( 4{ 5hour: { type: String, required: true }, 6active: { type: Boolean, required: true }, 7tests: [{ type: mongoose.Schema.Types.ObjectId, required: true }], 8packages: [{ type: mongoose.Schema.Types.ObjectId, required: true }], 9}, 10 { versionKey: false } 11 ); 12 13 module.exports = mongoose.model(”schedule”, scheduleSchema); Listing A.6: Tests Model 1const mongoose = require(”mongoose”); 2 3const testSchema = new mongoose.Schema( 4{ 5id: { type: Number, required: true }, 6module: { type: String, required: true }, 7name: { type: String, required: true }, 8description: { type: String, required: false }, 9defaultParam: { type: String, required: false }, 10 active: { type: Boolean, default:true }, 11 }, 12 { versionKey: false } 70
APPENDIX A. BACKEND CODE 13 ); 14 15 module.exports = mongoose.model(”test”, testSchema); A.2 Grammar Listing A.7: Lexer 1lexer grammar TestLexer; 2 3NEXT : '->' ; 4AND : '&' ; 5OR : '|' ; 6 7IF : '?' ; 8ELSE : ':' ; 9 10 RPAREN : ')' ; 11 LPAREN : '(' ; 12 13 END : ';' ; 14 15 KEYWORD : ([A-Za-z]+([/ _-][A-Za-z]+)*) 16 ; 17 18 WS 19 : [ \r\n\t] -> skip 20 ; Listing A.8: Parser 1parser grammar TestParser; 2 3options { 4tokenVocab=TestLexer; 5} 6 7test 8: statement END 9; 10 11 statement 12 : condition #Conditional 13 | seq #Sequence 14 ; 71
APPENDIX A. BACKEND CODE 15 16 condition 17 : expr IF statement ELSE statement #IfElse 18 | expr IF statement #If 19 ; 20 21 seq 22 : KEYWORD (NEXT statement)* 23 ; 24 25 expr 26 : LPAREN KEYWORD (AND KEYWORD)* RPAREN #And 27 | LPAREN KEYWORD (OR KEYWORD)* RPAREN #Or 28 ; Listing A.9: Visitor 1const antlr4 = require(”antlr4/index”); 2 3const get_id = (list, name) => { 4let test = list.filter((t) => t.name == name); 5return test[0]._id; 6}; 7 8const getId = (list, name) => { 9let test = list.filter((t) => t.name == name); 10 return test[0].id; 11 }; 12 13 const getDefaultParam = (list, name) => { 14 let test = list.filter((t) => t.name == name); 15 return test[0].defaultParam; 16 }; 17 18 const getModule = (list, name) => { 19 let test = list.filter((t) => t.name == name); 20 return test[0].module; 21 }; 22 23 // This class defines a complete generic visitor for a parse tree produced by TestParser. 24 25 function TestParserVisitor(listOfTests) { 26 antlr4.tree.ParseTreeVisitor.call(this); 27 this.tabs = 4; 28 this.auxOp = 0; 29 this.auxList = []; 72
APPENDIX A. BACKEND CODE 30 this.tests = []; 31 this.auxTests = listOfTests; 32 this.res = 33 'const TsimTest = require(”./../TsimTest/TsimTest”);\n\nmodule.exports.execute = () => ↩→{\n let results = [];\n\n'; 34 this.getRes = () => { 35 return this.res; 36 }; 37 this.getTests = () => { 38 return this.tests; 39 }; 40 return this; 41 } 42 43 TestParserVisitor.prototype = Object.create( 44 antlr4.tree.ParseTreeVisitor.prototype 45 ); 46 TestParserVisitor.prototype.constructor = TestParserVisitor; 47 48 // Visit a parse tree produced by TestParser#test. 49 TestParserVisitor.prototype.visitTest = function (ctx) { 50 this.visit(ctx.statement()); 51 this.res += ”\n” + ” ”.repeat(this.tabs) + ”return results;\n};\n”; 52 return ””; 53 }; 54 55 // Visit a parse tree produced by TestParser#Conditional. 56 TestParserVisitor.prototype.visitConditional = function (ctx) { 57 return this.visit(ctx.condition()); 58 }; 59 60 // Visit a parse tree produced by TestParser#Sequence. 61 TestParserVisitor.prototype.visitSequence = function (ctx) { 62 return this.visit(ctx.seq()); 63 }; 64 65 // Visit a parse tree produced by TestParser#IfElse. 66 TestParserVisitor.prototype.visitIfElse = function (ctx) { 67 this.visit(ctx.expr()); 68 let t = ” ”.repeat(this.tabs); 69 this.auxList.map((n) => { 70 let id = getId(this.auxTests, n); 71 let param = getDefaultParam(this.auxTests, n); 72 let module = getModule(this.auxTests, n); 73 n += ””; 73
APPENDIX A. BACKEND CODE 116 description: package.description, 117 code: package.code.replace(new RegExp(testName,”g”),''), 118 path: package.path, 119 tests: package.tests.filter(t => t !== idTest), 120 }); 121 }; 122 123 module.exports.deletePackage = async (idPackage) => { 124 let p = await Package.findOne({ _id: idPackage }).exec(); 125 try { 126 fs.unlinkSync(scripts_path + p.path); 127 return await Package.deleteOne({ _id: idPackage }); 128 }catch (err) { 129 throw err; 130 } 131 }; 132 133 module.exports.runPackage = async (idPackage) => { 134 let package = await Package.findOne({ _id: idPackage }).exec(); 135 if(package){ 136 const file = require(”./../public/Packages/” + package.path); 137 return await file.execute(); 138 } 139 return {}; 140 }; Listing A.12: Reports Controller 1const Report = require(”../models/reports”); 2 3module.exports.getReports = () => { 4return Report.find().sort({ date: -1 }).exec(); 5}; 6 7module.exports.getReport = (idReport) => { 8return Report.findOne({ _id: idReport }).exec(); 9}; 10 11 module.exports.insertReport = (report) => { 12 let r = new Report(report); 13 return r.save(); 14 }; 15 16 module.exports.updateReport = (idReport, report) => { 17 return Report.findOneAndUpdate({ _id: idReport }, report); 18 }; 80
APPENDIX A. BACKEND CODE 19 20 module.exports.deleteReport = (idReport) => { 21 return Report.deleteOne({ _id: idReport }); 22 }; Listing A.13: Schedules Controller 1const Schedule = require(”../models/schedules”); 2 3module.exports.getSchedules = () => { 4return Schedule.find().sort({ hour: 1 }).exec(); 5}; 6 7module.exports.getSchedule = (idSchedule) => { 8return Schedule.findOne({ _id: idSchedule }).exec(); 9}; 10 11 module.exports.insertSchedule = (schedule) => { 12 let s = new Schedule(schedule); 13 return s.save(); 14 }; 15 16 module.exports.updateSchedule = (idSchedule, schedule) => { 17 return Schedule.findOneAndUpdate({ _id: idSchedule }, schedule); 18 }; 19 20 module.exports.removeTestFromAll = (idTest) => { 21 return Schedule.updateMany( 22 { }, 23 { $pull: { tests: idTest } } 24 ).exec(); 25 }; 26 27 module.exports.removePackageFromAll = (idPackage) => { 28 return Schedule.updateMany( 29 { }, 30 { $pull: { packages: idPackage } } 31 ).exec(); 32 }; 33 34 module.exports.deleteSchedule = (idSchedule) => { 35 return Schedule.deleteOne({ _id: idSchedule }); 36 }; Listing A.14: Tests Controller 1const Test = require(”../models/tests”); 81
APPENDIX A. BACKEND CODE 2 3module.exports.getTests = () => { 4return Test.find({active: true}).sort({ id: 1 }).exec(); 5}; 6 7module.exports.getTest = (idTest) => { 8return Test.findOne({ _id: idTest }).exec(); 9}; 10 11 module.exports.insertTest = (test) => { 12 let t = new Test(test); 13 return t.save(); 14 }; 15 16 module.exports.updateTest = (idTest, newTest) => { 17 return Test.findOneAndUpdate({ _id: idTest }, newTest); 18 }; 19 20 module.exports.runTest = async (driversDirectory, idTest, defaultParam) => { 21 let test = await Test.findOne({ _id: idTest }).exec(); 22 let startTime = process.hrtime() 23 exec(`${driversDirectory}\\${test.module} ”${idTest}” ”${defaultParam}”`, (err, stdout, ↩→stderr) => { 24 if (err) return stderr 25 else { 26 let endTime = process.hrtime(startTime) 27 let result = JSON.parse(stdout) 28 result.runtime = (endTime[1] / 1000000).toFixed(3); 29 result.id_test = idTest; 30 result.module = test.module; 31 result.name = test.name; 32 return result; 33 } 34 }) 35 }; A.4 Routes Listing A.15: API Routes 1const express = require(”express”); 2const router = express.Router(); 3const exec = require('child_process').exec; 4const fs = require('fs'); 82
APPENDIX A. BACKEND CODE 5const jwt = require(”jsonwebtoken”); 6const cron = require(”node-cron”); 7const _ = require(”underscore”) 8const Tests = require(”../controllers/tests”); 9const Packages = require(”../controllers/packages”); 10 const Reports = require(”../controllers/reports”); 11 const Schedules = require(”../controllers/schedules”); 12 const Configurations = require(”../controllers/configurations”); 13 const TsimTest = require(”./../public/TsimTest/TsimTest”); 14 15 const checkForUpdates = async () => { 16 let newTests = await TsimTest.GetListOfTests(); 17 let oldTests = await Tests.getTests(); 18 let oldIds = oldTests.map(t => t.id) 19 let newIds = newTests.map(t => t.id) 20 let updates = 0 21 let unrwap = ({id, module, name, description, defaultParam}) => ({id, module, name, ↩→description, defaultParam}) 22 23 newTests.map(test => { 24 if(oldIds.includes(test.id)){ 25 let old = oldTests.filter(t => t.id === test.id)[0] 26 let checkOld = unrwap(old) 27 if(!(_.isEqual(test, checkOld))){ 28 Tests.updateTest(old._id, test) 29 .then() 30 .catch((error) => res.status(500).jsonp(error)); 31 updates = 1 32 } 33 } 34 else{ 35 Tests.insertTest(test) 36 .then() 37 .catch((error) => res.status(500).jsonp(error)); 38 updates = 1 39 } 40 }) 41 42 oldTests.map(old => { 43 if(!newIds.includes(old.id)){ 44 Tests.updateTest(old._id, Object.assign(old, {active: false})) 45 .then((dados) => { 46 Schedules.removeTestFromAll(old._id) 47 .then() 48 .catch((error) => res.status(500).jsonp(error)); 83
APPENDIX A. BACKEND CODE 49 Packages.getPackages() 50 .then((packages) => { 51 packages.map(p => { 52 if(p.tests.includes(old._id)){ 53 Schedules.removePackageFromAll(p._id) 54 .then() 55 .catch((error) => res.status(500).jsonp(error)); 56 Packages.deactivatePackageAndRemoveTest(p._id, p, old._id, old.name ↩→) 57 .then() 58 .catch((error) => res.status(500).jsonp(error)); 59 } 60 }) 61 }) 62 .catch((error) => res.status(500).jsonp(error)); 63 }) 64 .catch((error) => res.status(500).jsonp(error)); 65 updates = 1 66 } 67 }) 68 69 return updates 70 } 71 72 const generateBackupName = (options) => { 73 var tempDate = new Date(); 74 let tmpDay = tempDate.getDate() + ””; 75 let day = tmpDay.length === 1 ? ”0” + tmpDay : tmpDay; 76 let tmpMonth = tempDate.getMonth() + 1 + ””; 77 let month = tmpMonth.length === 1 ? ”0” + tmpMonth : tmpMonth; 78 let year = tempDate.getFullYear() + ””; 79 var tempHour = tempDate.getHours() + ””; 80 var tempMin = tempDate.getMinutes() + ””; 81 var tempSec = tempDate.getSeconds() + ””; 82 let hour = tempHour.length === 1 ? ”0” + tempHour : tempHour; 83 let min = tempMin.length === 1 ? ”0” + tempMin : tempMin; 84 let sec = tempSec.length === 1 ? ”0” + tempSec : tempSec; 85 var date = year + month + day + hour + min + sec; 86 return ”Backup_v” + date + ”_”+ (options.configurations ? ”c” : ””) + (options.reports ? ”r ↩→” : ””) + (options.schedules ? ”s” : ””) + (options.packages ? ”p” : ””); 87 }; 88 89 const getDate = () => { 90 var tempDate = new Date(); 91 let tmpDay = tempDate.getDate() + ””; 84
APPENDIX A. BACKEND CODE 92 let day = tmpDay.length === 1 ? ”0” + tmpDay : tmpDay; 93 let tmpMonth = tempDate.getMonth() + 1 + ””; 94 let month = tmpMonth.length === 1 ? ”0” + tmpMonth : tmpMonth; 95 let year = tempDate.getFullYear() + ””; 96 var tempHour = tempDate.getHours() + ””; 97 var tempMin = tempDate.getMinutes() + ””; 98 let hour = tempHour.length === 1 ? ”0” + tempHour : tempHour; 99 let min = tempMin.length === 1 ? ”0” + tempMin : tempMin; 100 var date = year + ”-” + month + ”-” + day + ” ” + hour + ”:” + min; 101 return date; 102 }; 103 104 const getHour = () => { 105 var tempDate = new Date(); 106 var tempHour = tempDate.getHours() + ””; 107 var tempMin = tempDate.getMinutes() + ””; 108 let hour = tempHour.length === 1 ? ”0” + tempHour : tempHour; 109 let min = tempMin.length === 1 ? ”0” + tempMin : tempMin; 110 var date = hour + ”:” + min; 111 return date; 112 }; 113 114 cron.schedule(”* * * * *”, function () { 115 Schedules.getSchedules() 116 .then(async (data) => { 117 let date = getDate(); 118 let hour = getHour(); 119 let list = data.filter((s) => s.hour === hour && s.active); 120 let listOfTests = list.map((s) => s.tests); 121 let listOfPackages = list.map((s) => s.packages); 122 123 if (listOfTests.length > 0 || listOfPackages.length > 0) { 124 let tests = listOfTests[0]; 125 let packages = listOfPackages[0]; 126 let results = []; 127 128 for (let i = 0; i < tests.length; i++) { 129 let r = await Tests.runTest(tests[i]); 130 results.push(r); 131 } 132 133 for (let i = 0; i < packages.length; i++) { 134 let r = await Packages.runPackage(packages[i]); 135 if(r[0]) 136 r.map((res) => results.push(res)); 85
APPENDIX A. BACKEND CODE 137 } 138 139 await Reports.insertReport({ 140 id_user: 5000, 141 date: date, 142 results: results, 143 }); 144 } 145 }) 146 .catch((error) => console.log(error)); 147 }); 148 149 router.get(”/”, function (req, res) { 150 checkForUpdates() 151 .then((data) => res.jsonp(data)) 152 .catch((error) => res.status(500).jsonp(error)); 153 }); 154 155 router.get(”/backups”, function (req, res) { 156 Configurations.getConfigurations() 157 .then((data) => 158 fs.readdir(data[0].backupDir, (err, files) => { 159 if(err) 160 res.status(500).jsonp(err) 161 else{ 162 let validFiles = [] 163 files.forEach(file => { 164 let name = file.split(”.zip”)[0]; 165 let words = name.split(”_v”) 166 if(words[1]){ 167 let ids = words[1].split(”_”) 168 if(words[0] === ”Backup” && /^\d+$/.test(ids[0]) && /[a-zA-Z]+/.test(ids ↩→[1])){ 169 validFiles.push(file) 170 } 171 } 172 }); 173 res.jsonp(validFiles) 174 } 175 }) 176 ) 177 .catch((error) => res.status(500).jsonp(error)); 178 }); 179 180 router.post(”/backups”, async function (req, res) { 86
APPENDIX A. BACKEND CODE 181 Configurations.getConfigurations() 182 .then((data) => { 183 let backupName = generateBackupName(req.body) 184 fs.mkdir(`${data[0].backupDir}\\${backupName}`, function(err) { 185 if (err) res.status(500).jsonp(err) 186 else { 187 fs.writeFile(`${data[0].backupDir}\\${backupName}\\info.json`, JSON.stringify( ↩→req.body, null, 4), (err) => { 188 if (err) res.status(500).jsonp(err) 189 else { 190 exec(`${data[0].appDir}\\bin\\exportBackup.bat ”${data[0].appDir}” ”${ ↩→data[0].dbBinDir}” ”${data[0].backupDir}” ”${req.body.packages}” ” ↩→${req.body.reports}” ”${req.body.schedules}” ”${req.body. ↩→configurations}” ${backupName}`, (err, stdout, stderr) => { 191 if (err) res.status(500).jsonp(err) 192 else res.jsonp(stdout) 193 }) 194 } 195 }) 196 } 197 }) 198 }) 199 .catch((error) => res.status(500).jsonp(error)); 200 }); 201 202 router.post(”/restore/:backup”, function (req, res) { 203 let strOptions = req.params.backup.split(”_”)[2].split(”.zip”)[0] 204 let reports = strOptions.includes(”r”) 205 let packages = strOptions.includes(”p”) 206 let schedules = strOptions.includes(”s”) 207 let configurations = strOptions.includes(”c”) 208 Configurations.getConfigurations() 209 .then((data) => { 210 exec(`${data[0].appDir}\\bin\\restoreBackup.bat ”${data[0].appDir}” ”${data[0]. ↩→dbBinDir}” ”${data[0].backupDir}” ${req.params.backup.split(”.”)[0]} ”${ ↩→packages}” ”${reports}” ”${schedules}” ”${configurations}”`, (err, stdout, ↩→stderr) => { 211 if (err) res.status(500).jsonp(err) 212 else res.jsonp(stdout) 213 }); 214 }) 215 .catch((error) => res.status(500).jsonp(error)); 216 }); 217 218 router.post(”/login”, function (req, res) { 87
APPENDIX A. BACKEND CODE 219 Configurations.getConfigurations() 220 .then((data) => { 221 if (req.body.id == data[0].userConfigurationID) { 222 res.jsonp( 223 jwt.sign({ id: req.body.id, role: ”admin” }, ”tsim_secret”, { 224 expiresIn: ”1h”, 225 }) 226 ); 227 }else { 228 res.jsonp( 229 jwt.sign({ id: req.body.id, role: ”worker” }, ”tsim_secret”, { 230 expiresIn: ”1h”, 231 }) 232 ); 233 } 234 }) 235 .catch((error) => res.status(500).jsonp(error)); 236 }); 237 238 // Configuration Requests 239 router.get(”/configurations”, function (req, res) { 240 Configurations.getConfigurations() 241 .then((data) => res.jsonp(data[0])) 242 .catch((error) => res.status(500).jsonp(error)); 243 }); 244 245 router.post(”/configurations”, function (req, res) { 246 Configurations.insertConfiguration(req.body) 247 .then((data) => res.jsonp(data)) 248 .catch((error) => res.status(500).jsonp(error)); 249 }); 250 251 router.put(”/configurations/:idConfiguration”, function (req, res) { 252 Configurations.updateConfiguration(req.params.idConfiguration, req.body) 253 .then((data) => res.jsonp(data)) 254 .catch((error) => res.status(500).jsonp(error)); 255 }); 256 257 // Test Requests 258 router.get(”/tests”, function (req, res) { 259 Tests.getTests() 260 .then((data) => res.jsonp(data)) 261 .catch((error) => res.status(500).jsonp(error)); 262 }); 263 88
APPENDIX A. BACKEND CODE 264 router.get(”/tests/:idTest”, function (req, res) { 265 Tests.getTest(req.params.idTest) 266 .then((data) => res.jsonp(data)) 267 .catch((error) => res.status(500).jsonp(error)); 268 }); 269 270 router.post(”/tests/run”, async function (req, res) { 271 let tests = req.body.tests; 272 let results = []; 273 274 for (let i = 0; i < tests.length; i++) { 275 let r = await Tests.runTest(tests[i]); 276 results.push(r); 277 } 278 await Reports.insertReport({ 279 id_user: req.body.id_user, 280 date: req.body.date, 281 results: results, 282 }); 283 res.jsonp(results); 284 }); 285 286 // Packages Requests 287 router.get(”/packages”, function (req, res) { 288 Packages.getPackages() 289 .then((data) => res.jsonp(data)) 290 .catch((error) => res.status(500).jsonp(error)); 291 }); 292 293 router.get(”/packages/:idPackage”, function (req, res) { 294 Packages.getPackage(req.params.idPackage) 295 .then((data) => res.jsonp(data)) 296 .catch((error) => res.status(500).jsonp(error)); 297 }); 298 299 router.post(”/packages”, function (req, res) { 300 Packages.insertPackage(req.body) 301 .then((data) => res.jsonp(data)) 302 .catch((error) => res.status(500).jsonp(error)); 303 }); 304 305 router.post(”/packages/run”, async function (req, res) { 306 let packages = req.body.packages; 307 let newReport = { 308 id_user: req.body.id_user, 89
APPENDIX B. FRONTEND CODE 155 const response = await axios.get(`${url}/schedules`); 156 return response.data; 157 }catch (error) { 158 const statusCode = error.response ? error.response.status : 500; 159 throw new Error(statusCode.toString()); 160 } 161 }; 162 163 export const insertSchedule = async (newSchedule) => { 164 try { 165 const response = await axios.post(`${url}/schedules`, newSchedule); 166 return response.data; 167 }catch (error) { 168 const statusCode = error.response ? error.response.status : 500; 169 throw new Error(statusCode.toString()); 170 } 171 }; 172 173 export const updateSchedule = async (id, newSchedule) => { 174 try { 175 const response = await axios.put(`${url}/schedules/${id}`, newSchedule); 176 return response.data; 177 }catch (error) { 178 const statusCode = error.response ? error.response.status : 500; 179 throw new Error(statusCode.toString()); 180 } 181 }; 182 183 export const deleteSchedule = async (id) => { 184 try { 185 const response = await axios.delete(`${url}/schedules/${id}`); 186 return response.data; 187 }catch (error) { 188 const statusCode = error.response ? error.response.status : 500; 189 throw new Error(statusCode.toString()); 190 } 191 }; 192 193 export const deletePackage = async (id) => { 194 try { 195 const response = await axios.delete(`${url}/packages/${id}`); 196 return response.data; 197 }catch (error) { 198 const statusCode = error.response ? error.response.status : 500; 199 throw new Error(statusCode.toString()); 96
APPENDIX B. FRONTEND CODE 200 } 201 }; 202 203 export const updatePackage = async (id, newPackage) => { 204 try { 205 const response = await axios.put(`${url}/packages/${id}`, newPackage); 206 return response.data; 207 }catch (error) { 208 return { errors: 1 }; 209 } 210 }; 211 212 export const insertPackage = async (newPackage) => { 213 try { 214 const response = await axios.post(`${url}/packages`, newPackage); 215 return response.data; 216 }catch (error) { 217 return { errors: 1 }; 218 } 219 }; B.2 Authentication Listing B.2: Authentication.js 1import Cookie from ”js-cookie”; 2import decode from ”jwt-decode”; 3 4export const getToken = () => { 5const token = Cookie.get(”tsimToken”); 6return token ? JSON.parse(token) : null; 7}; 8 9export const addToken = (encodedToken) => { 10 deleteToken(); 11 const decodedToken = decode(encodedToken); 12 const token = { 13 encoded: encodedToken, 14 ...decodedToken, 15 }; 16 Cookie.set(”tsimToken”, token, { expires: 1 }); 17 return token; 18 }; 19 97
APPENDIX B. FRONTEND CODE 20 export const deleteToken = () => Cookie.remove(”tsimToken”); 21 22 export const isAuthenticated = () => { 23 const token = getToken(); 24 if (!token) return false; 25 return token.role; 26 }; B.3 Routing Listing B.3: HomeRoute.js 1import React from ”react”; 2import { Route, Redirect } from ”react-router-dom”; 3import { isAuthenticated } from ”../../auth/auth”; 4 5const PrivateRoute = ({ children, path, ...rest }) => { 6let role = isAuthenticated(); 7return ( 8<Route 9{...rest} 10 render={(props) => 11 role === ”admin” ? ( 12 <Redirect to=”/configs” /> 13 ) : role === ”worker” ? ( 14 <Redirect to=”/execution” /> 15 ):( 16 children 17 ) 18 } 19 /> 20 ); 21 }; 22 23 export default PrivateRoute; Listing B.4: PrivateRoute.js 1import React from ”react”; 2import { Route, Redirect } from ”react-router-dom”; 3import { isAuthenticated } from ”../../auth/auth”; 4 5const PrivateRoute = ({ children, role, path, ...rest }) => ( 6<Route 7{...rest} 98
APPENDIX B. FRONTEND CODE 8render={(props) => 9isAuthenticated() === role ? children : <Redirect to=”/” /> 10 } 11 /> 12 ); 13 14 export default PrivateRoute; B.4 Pages Code Listing B.5: index.html 1<!DOCTYPE html> 2<html lang=”pt”> 3<head> 4<meta charset=”utf-8” /> 5<meta name=”viewport” content=”width=device-width, initial-scale=1” /> 6<title>TSIM Tests Aplication</title> 7</head> 8<body style=”font-family: Arial, Helvetica, sans-serif;”> 9<div id=”root”></div> 10 </body> 11 </html> Listing B.6: serviceWorker.js 1const isLocalhost = Boolean( 2window.location.hostname === ”localhost” || 3window.location.hostname === ”[::1]” || 4window.location.hostname.match( 5/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 6) 7); 8 9export function register(config) { 10 if (process.env.NODE_ENV === ”production” && ”serviceWorker” in navigator) { 11 const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 12 if (publicUrl.origin !== window.location.origin) { 13 return; 14 } 15 16 window.addEventListener(”load”, () => { 17 const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 18 19 if (isLocalhost) { 99
APPENDIX B. FRONTEND CODE 20 checkValidServiceWorker(swUrl, config); 21 navigator.serviceWorker.ready.then(() => { 22 console.log( 23 ”This web app is being served cache-first by a service ” + 24 ”worker. To learn more, visit https://bit.ly/CRA-PWA” 25 ); 26 }); 27 }else { 28 registerValidSW(swUrl, config); 29 } 30 }); 31 } 32 } 33 34 function registerValidSW(swUrl, config) { 35 navigator.serviceWorker 36 .register(swUrl) 37 .then((registration) => { 38 registration.onupdatefound = () => { 39 const installingWorker = registration.installing; 40 if (installingWorker == null) { 41 return; 42 } 43 installingWorker.onstatechange = () => { 44 if (installingWorker.state === ”installed”) { 45 if (navigator.serviceWorker.controller) { 46 console.log( 47 ”New content is available and will be used when all ” + 48 ”tabs for this page are closed. See https://bit.ly/CRA-PWA.” 49 ); 50 51 if (config && config.onUpdate) { 52 config.onUpdate(registration); 53 } 54 }else { 55 console.log(”Content is cached for offline use.”); 56 57 if (config && config.onSuccess) { 58 config.onSuccess(registration); 59 } 60 } 61 } 62 }; 63 }; 64 }) 100
APPENDIX B. FRONTEND CODE 65 .catch((error) => { 66 console.error(”Error during service worker registration:”, error); 67 }); 68 } 69 70 function checkValidServiceWorker(swUrl, config) { 71 fetch(swUrl, { 72 headers: { ”Service-Worker”: ”script” }, 73 }) 74 .then((response) => { 75 const contentType = response.headers.get(”content-type”); 76 if ( 77 response.status === 404 || 78 (contentType != null && 79 contentType.indexOf(”javascript”) === -1) 80 ) { 81 navigator.serviceWorker.ready.then((registration) => { 82 registration.unregister().then(() => { 83 window.location.reload(); 84 }); 85 }); 86 }else { 87 registerValidSW(swUrl, config); 88 } 89 }) 90 .catch(() => { 91 console.log( 92 ”No internet connection found. App is running in offline mode.” 93 ); 94 }); 95 } 96 97 export function unregister() { 98 if (”serviceWorker” in navigator) { 99 navigator.serviceWorker.ready 100 .then((registration) => { 101 registration.unregister(); 102 }) 103 .catch((error) => { 104 console.error(error.message); 105 }); 106 } 107 } Listing B.7: index.js 101
APPENDIX B. FRONTEND CODE 1import React from ”react”; 2import ReactDOM from ”react-dom”; 3import App from ”./App”; 4import * as serviceWorker from ”./serviceWorker”; 5 6ReactDOM.render(<App />, document.getElementById(”root”)); 7 8serviceWorker.unregister(); Listing B.8: App.js 1import React, {useEffect} from ”react”; 2import { BrowserRouter, Switch } from ”react-router-dom”; 3import Execution from ”./pages/Execution”; 4import Configs from ”./pages/Configs”; 5import Login from ”./pages/Login”; 6import PrivateRoute from ”./components/routes/PrivateRoute”; 7import HomeRoute from ”./components/routes/HomeRoute”; 8import {checkUpdates} from ”./api/api” 9 10 export default function App() { 11 12 useEffect(() => { 13 async function fetchData() { 14 await checkUpdates(); 15 } 16 fetchData(); 17 }, []); 18 19 return ( 20 <BrowserRouter> 21 <Switch> 22 <PrivateRoute path=”/execution” role=”worker”> 23 <Execution /> 24 </PrivateRoute> 25 <PrivateRoute path=”/configs” role=”admin”> 26 <Configs /> 27 </PrivateRoute> 28 <HomeRoute path=”/”> 29 <Login /> 30 </HomeRoute> 31 </Switch> 32 </BrowserRouter> 33 ); 34 } 102
APPENDIX B. FRONTEND CODE Listing B.9: Login Component 1import React, { useState } from ”react”; 2import { useHistory } from ”react-router-dom”; 3import AppBar from ”@material-ui/core/AppBar”; 4import Toolbar from ”@material-ui/core/Toolbar”; 5import Typography from ”@material-ui/core/Typography”; 6import Button from ”@material-ui/core/Button”; 7import TextField from ”@material-ui/core/TextField”; 8import Card from '@material-ui/core/Card'; 9import Grid from '@material-ui/core/Grid'; 10 import CardContent from '@material-ui/core/CardContent'; 11 import { login, getConfigurations } from ”../api/api”; 12 import { addToken } from ”../auth/auth”; 13 14 export default function Login() { 15 const history = useHistory(); 16 const [id, setId] = useState(””); 17 const [error, setError] = useState(false); 18 19 const handleChange = (event) => { 20 const { name, value } = event.target; 21 if (name === ”id”) setId(value); 22 }; 23 24 const handleClickExecution = async () => { 25 let configurations = await getConfigurations(); 26 if (id === ”” || id === configurations.userConfigurationID) { 27 setError(true); 28 }else { 29 setError(false); 30 let res = await login(id); 31 await addToken(res); 32 history.push(”/execution”); 33 } 34 }; 35 36 const handleClickConfigs = async () => { 37 let configurations = await getConfigurations(); 38 if (id === ”” || id !== configurations.userConfigurationID) { 39 setError(true); 40 }else { 41 setError(false); 42 let res = await login(id); 43 await addToken(res); 44 history.push(”/configs”); 103
APPENDIX B. FRONTEND CODE 45 } 46 }; 47 48 return ( 49 <div style={{ textAlign: ”center” }}> 50 <AppBar position=”static” style={{ alignItems: ”center” }}> 51 <Toolbar> 52 <Typography variant=”h3”> 53 Sistema de Autodiagnóstico dos Sistemas de Teste 54 Automático 55 </Typography> 56 </Toolbar> 57 </AppBar> 58 <Grid 59 container 60 spacing={0} 61 direction=”column” 62 alignItems=”center” 63 justify=”center” 64 style={{ minHeight: '70vh' }} 65 > 66 <Grid item xs={12}> 67 <Card style={{width: 600, textAlign: ”center”}}> 68 <CardContent> 69 <h1>Escolha o modo de execução:</h1> 70 <TextField 71 name=”id” 72 error={error} 73 label=”ID Funcionário” 74 variant=”outlined” 75 value={id} 76 onChange={handleChange} 77 /> 78 <br /> 79 <br /> 80 <Button 81 style={{ marginRight: 20 }} 82 variant=”contained” 83 color=”primary” 84 size=”large” 85 disableElevation 86 onClick={handleClickExecution} 87 > 88 Execução 89 </Button> 104
APPENDIX B. FRONTEND CODE 90 <Button 91 style={{ marginLeft: 20 }} 92 variant=”contained” 93 color=”primary” 94 size=”large” 95 disableElevation 96 onClick={handleClickConfigs} 97 > 98 Configuração 99 </Button> 100 </CardContent> 101 </Card> 102 </Grid> 103 </Grid> 104 </div> 105 ); 106 } B.4.1 Execution Components Listing B.10: Execution.js 1import React from ”react”; 2import { useHistory } from ”react-router-dom”; 3import { deleteToken } from ”../auth/auth”; 4import Button from ”@material-ui/core/Button”; 5import Typography from ”@material-ui/core/Typography”; 6import AppBar from ”@material-ui/core/AppBar”; 7import Toolbar from ”@material-ui/core/Toolbar”; 8import Test from ”../components/execution/tests/Test”; 9 10 export default function Execution() { 11 const history = useHistory(); 12 13 const logout = async () => { 14 await deleteToken(); 15 history.push(”/”); 16 }; 17 18 return ( 19 <div> 20 <AppBar position=”static”> 21 <Toolbar> 22 <Typography variant=”h4” style={{ flexGrow: 1, textAlign: ”center” }}> 23 Execução de Testes 105
APPENDIX B. FRONTEND CODE 175 <Button 176 variant=”outlined” 177 className={classes.button} 178 onClick={handleCheckedLeft} 179 disabled={rightChecked.length === 0} 180 > 181 < 182 </Button> 183 </Grid> 184 </Grid> 185 <Grid item>{customList(”Executar”, right)}</Grid> 186 </Grid> 187 <br /> 188 <ResponsiveButton 189 tests={right} 190 execute={props.execute} 191 clean={cleanList} 192 /> 193 </div> 194 ); 195 } Listing B.13: ExecuteButton.js 1import React from ”react”; 2import Button from ”@material-ui/core/Button”; 3import Dialog from ”@material-ui/core/Dialog”; 4import DialogActions from ”@material-ui/core/DialogActions”; 5import DialogContent from ”@material-ui/core/DialogContent”; 6import DialogContentText from ”@material-ui/core/DialogContentText”; 7import DialogTitle from ”@material-ui/core/DialogTitle”; 8import useMediaQuery from ”@material-ui/core/useMediaQuery”; 9import { useTheme } from ”@material-ui/core/styles”; 10 11 export default function ResponsiveButton(props) { 12 const [open, setOpen] = React.useState(false); 13 const [noTest, setNoTest] = React.useState(false); 14 const theme = useTheme(); 15 const fullScreen = useMediaQuery(theme.breakpoints.down(”sm”)); 16 17 const handleClickOpen = () => { 18 props.tests.length === 0 ? setNoTest(true) : setOpen(true); 19 }; 20 21 const handleClose = () => { 22 setNoTest(false) 112
APPENDIX B. FRONTEND CODE 23 setOpen(false); 24 }; 25 26 const handleExecute = () => { 27 setOpen(false); 28 props.clean(); 29 props.execute(props.tests); 30 }; 31 32 return ( 33 <div> 34 <Button 35 variant=”outlined” 36 color=”primary” 37 onClick={handleClickOpen} 38 > 39 Executar 40 </Button> 41 <Dialog 42 disableBackdropClick={true} 43 fullScreen={fullScreen} 44 open={noTest} 45 > 46 <DialogTitle> 47 {”Tem que selecionar pelo menos um teste.”} 48 </DialogTitle> 49 <DialogActions> 50 <Button onClick={handleClose} color=”primary” autoFocus> 51 Ok 52 </Button> 53 </DialogActions> 54 </Dialog> 55 <Dialog 56 disableBackdropClick={true} 57 fullScreen={fullScreen} 58 open={open} 59 > 60 <DialogTitle> 61 {”Tem a certeza que quer executar estes testes?”} 62 </DialogTitle> 63 <DialogContent> 64 <DialogContentText> 65 Selecionou {props.tests.length} testes. 66 </DialogContentText> 67 </DialogContent> 113
APPENDIX B. FRONTEND CODE 68 <DialogActions> 69 <Button autoFocus onClick={handleClose} color=”primary”> 70 Não 71 </Button> 72 <Button onClick={handleExecute} color=”primary” autoFocus> 73 Sim 74 </Button> 75 </DialogActions> 76 </Dialog> 77 </div> 78 ); 79 } Listing B.14: ResultsTable.js 1import React from ”react”; 2import Button from ”@material-ui/core/Button”; 3import Dialog from ”@material-ui/core/Dialog”; 4import DialogActions from ”@material-ui/core/DialogActions”; 5import DialogContent from ”@material-ui/core/DialogContent”; 6import DialogTitle from ”@material-ui/core/DialogTitle”; 7import Table from ”@material-ui/core/Table”; 8import TableBody from ”@material-ui/core/TableBody”; 9import TableCell from ”@material-ui/core/TableCell”; 10 import TableContainer from ”@material-ui/core/TableContainer”; 11 import TableHead from ”@material-ui/core/TableHead”; 12 import TableRow from ”@material-ui/core/TableRow”; 13 import Paper from ”@material-ui/core/Paper”; 14 import FiberManualRecordIcon from ”@material-ui/icons/FiberManualRecord”; 15 16 export default function ResultsTable(props) { 17 const [open, setOpen] = React.useState(true); 18 19 const handleClose = () => { 20 props.setResults([]); 21 setOpen(false); 22 }; 23 24 return ( 25 <Dialog maxWidth=”lg” disableBackdropClick={true} open={open}> 26 <DialogTitle>{”Relatório dos Testes efetuados”}</DialogTitle> 27 <DialogContent> 28 <TableContainer component={Paper}> 29 <Table> 30 <TableHead> 31 <TableRow> 114
APPENDIX B. FRONTEND CODE 32 <TableCell> 33 <b>Módulo</b> 34 </TableCell> 35 <TableCell align=”center”> 36 <b>Teste</b> 37 </TableCell> 38 <TableCell align=”center”> 39 <b>Tempo de Execução</b> 40 </TableCell> 41 <TableCell align=”center”> 42 <b>Mensagem</b> 43 </TableCell> 44 <TableCell align=”center”> 45 <b>Valor</b> 46 </TableCell> 47 <TableCell align=”right”> 48 <b>Resultado</b> 49 </TableCell> 50 </TableRow> 51 </TableHead> 52 <TableBody> 53 {props.results.map((test) => { 54 return ( 55 <TableRow key={test._id}> 56 <TableCell component=”th” scope=”row”> 57 {test.module} 58 </TableCell> 59 <TableCell align=”center”> 60 {test.name} 61 </TableCell> 62 <TableCell align=”center”> 63 {test.runtime}ms 64 </TableCell> 65 <TableCell align=”center”> 66 {test.message} 67 </TableCell> 68 <TableCell align=”center”> 69 {test.resultValue} 70 </TableCell> 71 <TableCell align=”right”> 72 {test.result === ”success” 73 ? ”Passou” 74 : (test.result === ”fail” ? ”Falhou” : ”Inconclusivo”)} 75 {test.result === ”success” ? ( 76 <FiberManualRecordIcon 115
APPENDIX B. FRONTEND CODE 77 style={{ 78 color: ”green”, 79 fontSize: ”12px”, 80 verticalAlign: ”center”, 81 }} 82 /> 83 ) : ( test.result === ”fail” ? 84 <FiberManualRecordIcon 85 style={{ 86 color: ”red”, 87 fontSize: ”12px”, 88 verticalAlign: ”center”, 89 }} 90 /> : 91 <FiberManualRecordIcon 92 style={{ 93 color: ”yellow”, 94 fontSize: ”12px”, 95 verticalAlign: ”center”, 96 }} 97 /> 98 )} 99 </TableCell> 100 </TableRow> 101 ); 102 })} 103 </TableBody> 104 </Table> 105 </TableContainer> 106 </DialogContent> 107 <DialogActions> 108 <Button autoFocus onClick={handleClose} color=”primary”> 109 Fechar 110 </Button> 111 </DialogActions> 112 </Dialog> 113 ); 114 } B.4.2 Configuration Components Listing B.15: Configuration.js 1import React, { useState } from ”react”; 2import { useHistory } from ”react-router-dom”; 116
APPENDIX B. FRONTEND CODE 3import { deleteToken } from ”../auth/auth”; 4import Button from ”@material-ui/core/Button”; 5import AppBar from ”@material-ui/core/AppBar”; 6import Toolbar from ”@material-ui/core/Toolbar”; 7import Schedules from ”../components/configs/schedules/Schedules”; 8import Docs from ”../components/configs/docs/Docs”; 9import Packages from ”../components/configs/packages/Packages”; 10 import Historico from ”../components/execution/reports/Historico”; 11 import Configuracoes from ”../components/configs/configurations/Configurations”; 12 13 export default function Configs() { 14 const history = useHistory(); 15 const [section, setSection] = useState(”Relatórios”); 16 17 const logout = async () => { 18 await deleteToken(); 19 history.push(”/”); 20 }; 21 22 return ( 23 <div> 24 <AppBar position=”static”> 25 <Toolbar> 26 <Button 27 color=”inherit” 28 onClick={() => setSection(”Relatórios”)} 29 style={{ flexGrow: 1 }} 30 > 31 Relatórios 32 </Button> 33 <Button 34 color=”inherit” 35 onClick={() => setSection(”Agendamentos”)} 36 style={{ flexGrow: 1 }} 37 > 38 Agendamentos 39 </Button> 40 <Button 41 color=”inherit” 42 onClick={() => setSection(”Testes”)} 43 style={{ flexGrow: 1 }} 44 > 45 Gestão de Pacotes 46 </Button> 47 <Button 117
APPENDIX B. FRONTEND CODE 48 color=”inherit” 49 onClick={() => setSection(”Documentação”)} 50 style={{ flexGrow: 1 }} 51 > 52 Documentação 53 </Button> 54 <Button 55 color=”inherit” 56 onClick={() => setSection(”Configurações”)} 57 style={{ flexGrow: 1 }} 58 > 59 Configurações 60 </Button> 61 <Button color=”inherit” onClick={logout}> 62 Sair 63 </Button> 64 </Toolbar> 65 </AppBar> 66 { 67 section === ”Agendamentos” ? <Schedules /> : 68 section === ”Testes” ? <Packages /> : 69 section === ”Documentação” ? <Docs /> : 70 section === ”Configurações” ? <Configuracoes /> : <Historico /> 71 } 72 </div> 73 ); 74 } Listing B.16: Reports.js 1import React, { useEffect, useState } from ”react”; 2import ReportsTable from ”./ReportsTable”; 3import { getReports } from ”../../../api/api”; 4 5export default function Historico() { 6const [loading, setLoading] = useState(false); 7const [data, setData] = useState([]); 8 9useEffect(() => { 10 setLoading(true); 11 async function fetchData() { 12 let reports = await getReports(); 13 setData(reports); 14 setLoading(false); 15 } 16 fetchData(); 118
APPENDIX B. FRONTEND CODE 17 }, []); 18 19 return ( 20 <div> 21 {loading ? ( 22 <h3 style={{ textAlign: ”center” }}>A carregar dados...</h3> 23 ):( 24 <ReportsTable data={data} /> 25 )} 26 </div> 27 ); 28 } Listing B.17: ReportsTableWithDetail.js 1import React, {useState} from 'react'; 2import { makeStyles } from '@material-ui/core/styles'; 3import Paper from '@material-ui/core/Paper'; 4import Table from '@material-ui/core/Table'; 5import TableBody from '@material-ui/core/TableBody'; 6import TableCell from '@material-ui/core/TableCell'; 7import TableContainer from '@material-ui/core/TableContainer'; 8import TableHead from '@material-ui/core/TableHead'; 9import TablePagination from '@material-ui/core/TablePagination'; 10 import TableRow from '@material-ui/core/TableRow'; 11 import FiberManualRecordIcon from ”@material-ui/icons/FiberManualRecord”; 12 import IconButton from '@material-ui/core/IconButton'; 13 import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; 14 import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; 15 import Box from '@material-ui/core/Box'; 16 import Collapse from '@material-ui/core/Collapse'; 17 import ReportDetail from ”./ReportDetail” 18 19 const useStyles = makeStyles({ 20 root: { 21 margin: ”1%”, 22 width: '98%', 23 }, 24 container: { 25 maxHeight: 770, 26 }, 27 }); 28 29 const useRowStyles = makeStyles({ 30 root: { 31 '& > *': { 119
APPENDIX B. FRONTEND CODE 32 borderBottom: 'unset', 33 }, 34 }, 35 }); 36 37 function Row(props) { 38 const { row } = props; 39 const [open, setOpen] = useState(false); 40 const classes = useRowStyles(); 41 42 return ( 43 <React.Fragment> 44 <TableRow hover className={classes.root}> 45 <TableCell>{row.id_user}</TableCell> 46 <TableCell align=”center”>{row.date}</TableCell> 47 <TableCell align=”center”>{row.results.map(r => r.runtime).reduce((a, b) => a + b, ↩→0).toFixed(3)}ms</TableCell> 48 <TableCell align=”center”> 49 {row.results.filter(r => r.result === ”success”).length}/{row.results.length}{” ↩ → ”} 50 <FiberManualRecordIcon 51 style={{ 52 color: row.results.filter(r => r.result === ”fail”).length > 0 53 ? ”red” 54 : row.results.filter(r => r.result === ”inconclusive”).length > 0 55 ? ”yellow” 56 : ”green”, 57 fontSize: ”12px”, 58 }} 59 /> 60 </TableCell> 61 <TableCell align=”center”> 62 <IconButton size=”small” onClick={() => setOpen(!open)}> 63 {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} 64 </IconButton> 65 </TableCell> 66 </TableRow> 67 <TableRow> 68 <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}> 69 <Collapse in={open} timeout=”auto” unmountOnExit> 70 <Box margin={1}> 71 <ReportDetail report={row.results} /> 72 </Box> 73 </Collapse> 74 </TableCell> 120
APPENDIX B. FRONTEND CODE 75 </TableRow> 76 </React.Fragment> 77 ); 78 } 79 80 export default function ReportsTable(props) { 81 const classes = useStyles(); 82 const [page, setPage] = useState(0); 83 const [rowsPerPage, setRowsPerPage] = useState(11); 84 85 const handleChangePage = (event, newPage) => { 86 setPage(newPage); 87 }; 88 89 const handleChangeRowsPerPage = (event) => { 90 setRowsPerPage(+event.target.value); 91 setPage(0); 92 }; 93 94 return ( 95 <Paper className={classes.root}> 96 <TableContainer className={classes.container}> 97 <Table stickyHeader> 98 <TableHead> 99 <TableRow> 100 <TableCell><b>ID Funcionário</b></TableCell> 101 <TableCell align=”center”><b>Data de Execução</b></TableCell> 102 <TableCell align=”center”><b>Tempo de Execução</b></TableCell> 103 <TableCell align=”center”><b>Resultados</b></TableCell> 104 <TableCell align=”center”/> 105 </TableRow> 106 </TableHead> 107 <TableBody> 108 {props.data.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map ↩→((row) => <Row row={row} />)} 109 </TableBody> 110 </Table> 111 </TableContainer> 112 <TablePagination 113 rowsPerPageOptions={[11]} 114 labelRowsPerPage=”Relatórios por Página:” 115 component=”div” 116 count={props.data.length} 117 rowsPerPage={rowsPerPage} 118 page={page} 121
APPENDIX B. FRONTEND CODE 128 } 129 /> 130 <ListItemSecondaryAction> 131 <Switch 132 color=”primary” 133 edge=”end” 134 onChange={() => handleToggle(s._id, s)} 135 checked={s.active} 136 /> 137 <IconButton 138 onClick={() => handleDelete(s._id)} 139 > 140 <DeleteIcon color=”action” /> 141 </IconButton> 142 </ListItemSecondaryAction> 143 </ListItem> 144 ); 145 })} 146 </List> 147 </div> 148 <div> 149 <Fab 150 color=”primary” 151 className={classes.fab} 152 onClick={() => setAdd(true)} 153 > 154 <AddIcon /> 155 </Fab> 156 </div> 157 <div> 158 {add ? ( 159 <AddSchedule 160 add={add} 161 setAdd={setAdd} 162 tests={props.tests} 163 handleInsert={handleInsert} 164 packages={props.packages} 165 /> 166 ):( 167 ”” 168 )} 169 {update ? ( 170 <UpdateSchedule 171 update={update} 172 setUpdate={setUpdate} 128
APPENDIX B. FRONTEND CODE 173 schedule={schedule} 174 tests={props.tests} 175 packages={props.packages} 176 handleUpdate={handleUpdateSchedule} 177 /> 178 ):( 179 ”” 180 )} 181 </div> 182 </div> 183 ); 184 } Listing B.21: AddSchedule.js 1import React, { useState } from ”react”; 2import { makeStyles } from ”@material-ui/core/styles”; 3import Button from ”@material-ui/core/Button”; 4import Dialog from ”@material-ui/core/Dialog”; 5import DialogActions from ”@material-ui/core/DialogActions”; 6import DialogContent from ”@material-ui/core/DialogContent”; 7import DialogTitle from ”@material-ui/core/DialogTitle”; 8import useMediaQuery from ”@material-ui/core/useMediaQuery”; 9import { useTheme } from ”@material-ui/core/styles”; 10 import Switch from ”@material-ui/core/Switch”; 11 import Checkbox from ”@material-ui/core/Checkbox”; 12 import TextField from ”@material-ui/core/TextField”; 13 import Alert from ”@material-ui/lab/Alert”; 14 import Table from '@material-ui/core/Table'; 15 import TableBody from '@material-ui/core/TableBody'; 16 import TableCell from '@material-ui/core/TableCell'; 17 import TableContainer from '@material-ui/core/TableContainer'; 18 import TableHead from '@material-ui/core/TableHead'; 19 import TableRow from '@material-ui/core/TableRow'; 20 import Paper from '@material-ui/core/Paper'; 21 import IconButton from '@material-ui/core/IconButton'; 22 import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; 23 import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; 24 25 const useStyles = makeStyles((theme) => ({ 26 form: { 27 textAlign: ”center”, 28 }, 29 formControl: { 30 margin: theme.spacing(2), 31 width: 300, 129
APPENDIX B. FRONTEND CODE 32 }, 33 })); 34 35 export default function AddSchedule(props) { 36 const theme = useTheme(); 37 const classes = useStyles(); 38 const fullScreen = useMediaQuery(theme.breakpoints.down(”sm”)); 39 const [hour, setHour] = useState(”08:00”); 40 const [active, setActive] = useState(false); 41 const [tests, setTests] = useState([]); 42 const [packages, setPackages] = useState([]); 43 const [alert, setAlert] = useState(false); 44 const [openTests, setOpenTests] = useState(false); 45 const [openPackages, setOpenPackages] = useState(false); 46 47 const handleChangeHour = (event) => { 48 setHour(event.target.value); 49 }; 50 51 const handleChangePackages = (event, value) => { 52 if(value === ”Todos”){ 53 if(props.packages.length === packages.length) setPackages([]) 54 else setPackages(props.packages.map(p => p._id)) 55 } 56 else{ 57 packages.includes(value) === true 58 ? setPackages( 59 packages.filter((id) => { 60 if (id === value) return false; 61 else return true; 62 }) 63 ) 64 : setPackages(packages.concat(value)); 65 } 66 }; 67 68 const handleChangeTests = (event, value) => { 69 if(value === ”Todos”){ 70 if(props.tests.length === tests.length) setTests([]) 71 else setTests(props.tests.map(t => t._id)) 72 } 73 else{ 74 tests.includes(value) === true 75 ? setTests( 76 tests.filter((id) => { 130
APPENDIX B. FRONTEND CODE 77 if (id === value) return false; 78 else return true; 79 }) 80 ) 81 : setTests(tests.concat(value)); 82 } 83 }; 84 85 const handleClose = () => { 86 props.setAdd(false); 87 setHour(”08:00”); 88 setActive(false); 89 setTests([]); 90 setPackages([]); 91 }; 92 93 const handleInsert = async () => { 94 if (hour === ”” || (tests.length === 0 && packages.length === 0)) { 95 setAlert(true); 96 }else { 97 let newSchedule = { 98 hour: hour, 99 active: active, 100 tests: tests, 101 packages: packages 102 }; 103 props.handleInsert(newSchedule); 104 props.setAdd(false); 105 setHour(”08:00”); 106 setActive(false); 107 setTests([]); 108 setPackages([]); 109 } 110 }; 111 112 let displayAlert = 113 alert === false ? ( 114 ”” 115 ):( 116 <div> 117 <Alert 118 severity=”error” 119 variant=”filled” 120 onClose={() => { 121 setAlert(false); 131
APPENDIX B. FRONTEND CODE 122 }} 123 > 124 Tem que preencher os campos todos! 125 </Alert> 126 <br /> 127 </div> 128 ); 129 130 return ( 131 <Dialog 132 disableBackdropClick 133 fullScreen={fullScreen} 134 open={props.add} 135 onClose={handleClose} 136 > 137 <DialogTitle>Adicionar Novo Agendamento</DialogTitle> 138 <DialogContent> 139 <div className={classes.form}> 140 <TextField 141 label=”Hora” 142 type=”time” 143 name=”hour” 144 value={hour} 145 onChange={handleChangeHour} 146 inputProps={{ step: 900 }} 147 /> 148 <Switch 149 color=”primary” 150 onChange={() => setActive((prev) => !prev)} 151 checked={active} 152 /> 153 </div> 154 <br/> 155 <TableContainer component={Paper} style={{maxHeight: 300, width: 350}}> 156 <Table size=”small”> 157 <TableHead> 158 <TableRow onClick={() => setOpenTests(!openTests)} style={{cursor: ” ↩→pointer”}}> 159 <TableCell> 160 <IconButton > 161 {openTests ? <KeyboardArrowUpIcon fontSize=”large” /> : < ↩→KeyboardArrowDownIcon fontSize=”large” />} 162 </IconButton> 163 </TableCell> 164 <TableCell> 132
APPENDIX B. FRONTEND CODE 165 <h3>Testes a executar</h3> 166 </TableCell> 167 </TableRow> 168 </TableHead> 169 <TableBody> 170 { 171 openTests ? 172 <TableRow key={”Todos”}> 173 <TableCell> 174 <Checkbox 175 checked={tests.length === props.tests.length} 176 color=”primary” 177 onClick={(event) => handleChangeTests(event, ”Todos”)} 178 /> 179 </TableCell> 180 <TableCell>Todos</TableCell> 181 </TableRow> : ”” 182 } 183 { 184 openTests ? 185 props.tests.map((t) => ( 186 <TableRow key={t._id}> 187 <TableCell> 188 <Checkbox 189 checked={tests.includes(t._id)} 190 color=”primary” 191 onClick={(event) => handleChangeTests(event, t._id)} 192 /> 193 </TableCell> 194 <TableCell>{t.name}</TableCell> 195 </TableRow> 196 )) : ”” 197 } 198 </TableBody> 199 </Table> 200 </TableContainer> 201 <br/> 202 <TableContainer component={Paper} style={{maxHeight: 300, width: 350}}> 203 <Table size=”small”> 204 <TableHead> 205 <TableRow onClick={() => setOpenPackages(!openPackages)} style={{cursor: ↩→”pointer”}}> 206 <TableCell> 207 <IconButton > 133
APPENDIX B. FRONTEND CODE 208 {openPackages ? <KeyboardArrowUpIcon fontSize=”large” /> : < ↩→KeyboardArrowDownIcon fontSize=”large” />} 209 </IconButton> 210 </TableCell> 211 <TableCell> 212 <h3>Pacotes a executar</h3> 213 </TableCell> 214 </TableRow> 215 </TableHead> 216 <TableBody> 217 { 218 openPackages ? 219 <TableRow key={”Todos”}> 220 <TableCell> 221 <Checkbox 222 checked={packages.length === props.packages.length} 223 color=”primary” 224 onClick={(event) => handleChangePackages(event, ”Todos”)} 225 /> 226 </TableCell> 227 <TableCell>Todos</TableCell> 228 </TableRow> : ”” 229 } 230 { 231 openPackages ? 232 props.packages.map((t) => ( 233 <TableRow key={t._id}> 234 <TableCell> 235 <Checkbox 236 checked={packages.includes(t._id)} 237 color=”primary” 238 onClick={(event) => handleChangePackages(event, t._id)} 239 /> 240 </TableCell> 241 <TableCell>{t.name}</TableCell> 242 </TableRow> 243 )) : ”” 244 } 245 </TableBody> 246 </Table> 247 </TableContainer> 248 {displayAlert} 249 </DialogContent> 250 <DialogActions> 251 <Button autoFocus onClick={handleClose} color=”primary”> 134
APPENDIX B. FRONTEND CODE 252 Cancelar 253 </Button> 254 <Button autoFocus onClick={handleInsert} color=”primary”> 255 Adicionar 256 </Button> 257 </DialogActions> 258 </Dialog> 259 ); 260 } Listing B.22: UpdateSchedule.js 1import React, { useState } from ”react”; 2import { makeStyles } from ”@material-ui/core/styles”; 3import Button from ”@material-ui/core/Button”; 4import Dialog from ”@material-ui/core/Dialog”; 5import DialogActions from ”@material-ui/core/DialogActions”; 6import DialogContent from ”@material-ui/core/DialogContent”; 7import DialogTitle from ”@material-ui/core/DialogTitle”; 8import useMediaQuery from ”@material-ui/core/useMediaQuery”; 9import { useTheme } from ”@material-ui/core/styles”; 10 import Switch from ”@material-ui/core/Switch”; 11 import Checkbox from ”@material-ui/core/Checkbox”; 12 import TextField from ”@material-ui/core/TextField”; 13 import Alert from ”@material-ui/lab/Alert”; 14 import Table from '@material-ui/core/Table'; 15 import TableBody from '@material-ui/core/TableBody'; 16 import TableCell from '@material-ui/core/TableCell'; 17 import TableContainer from '@material-ui/core/TableContainer'; 18 import TableHead from '@material-ui/core/TableHead'; 19 import TableRow from '@material-ui/core/TableRow'; 20 import Paper from '@material-ui/core/Paper'; 21 import IconButton from '@material-ui/core/IconButton'; 22 import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; 23 import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; 24 25 const useStyles = makeStyles((theme) => ({ 26 form: { 27 textAlign: ”center”, 28 }, 29 formControl: { 30 margin: theme.spacing(2), 31 width: 300, 32 }, 33 })); 34 135
APPENDIX B. FRONTEND CODE 35 export default function UpdateSchedule(props) { 36 const theme = useTheme(); 37 const classes = useStyles(); 38 const fullScreen = useMediaQuery(theme.breakpoints.down(”sm”)); 39 const [hour, setHour] = useState(props.schedule.hour); 40 const [active, setActive] = useState(props.schedule.active); 41 const [tests, setTests] = useState(props.schedule.tests); 42 const [alert, setAlert] = useState(false); 43 const [packages, setPackages] = useState(props.schedule.packages); 44 const [openTests, setOpenTests] = useState(false); 45 const [openPackages, setOpenPackages] = useState(false); 46 47 const handleChangeHour = (event) => { 48 setHour(event.target.value); 49 }; 50 51 const handleChangePackages = (event, value) => { 52 if(value === ”Todos”){ 53 if(props.packages.length === packages.length) setPackages([]) 54 else setPackages(props.packages.map(p => p._id)) 55 } 56 else{ 57 packages.includes(value) === true 58 ? setPackages( 59 packages.filter((id) => { 60 if (id === value) return false; 61 else return true; 62 }) 63 ) 64 : setPackages(packages.concat(value)); 65 } 66 }; 67 68 const handleChangeTests = (event, value) => { 69 if(value === ”Todos”){ 70 if(props.tests.length === tests.length) setTests([]) 71 else setTests(props.tests.map(t => t._id)) 72 } 73 else{ 74 tests.includes(value) === true 75 ? setTests( 76 tests.filter((id) => { 77 if (id === value) return false; 78 else return true; 79 }) 136
APPENDIX B. FRONTEND CODE 80 ) 81 : setTests(tests.concat(value)); 82 } 83 }; 84 85 const handleClose = () => { 86 props.setUpdate(false); 87 }; 88 89 const handleUpdate = async () => { 90 if (hour === ”” || (tests.length === 0 && packages.length === 0)) { 91 setAlert(true); 92 }else { 93 let newSchedule = { 94 hour: hour, 95 active: active, 96 tests: tests, 97 packages: packages 98 }; 99 props.handleUpdate(props.schedule._id, newSchedule) 100 } 101 }; 102 103 let displayAlert = 104 alert === false ? ( 105 ”” 106 ):( 107 <div> 108 <Alert 109 severity=”error” 110 variant=”filled” 111 onClose={() => { 112 setAlert(false); 113 }} 114 > 115 Tem que preencher os campos todos! 116 </Alert> 117 <br /> 118 </div> 119 ); 120 121 return ( 122 <Dialog 123 disableBackdropClick 124 fullScreen={fullScreen} 137