Using Functional Specification to Detect Failed Error Propagation in Stateful Services: An Industrial Case-Study

preprint OA: closed
📄 Open PDF Full text JSON View at publisher

Abstract

In stateful transactional services, latent internal errors often fail to manifest at the output level, eluding traditional black-box testing methods. This phenomenon, known as Failed Error Propagation (FEP), represents a critical blind spot in software quality assurance, particularly in business-critical domains such as financial trading systems. In this paper, we present a novel specification-based testing approach for FEP detection using high-level functional specifications written in Haskell. Our method integrates definition-use (du) path coverage analysis into the test generation process and leverages search-based techniques to synthesize test suites that increase the probability of exposing FEP. We evaluate our approach on a production-grade stock exchange order matching engine and uncover two previously undetected FEP-related bugs, demonstrating the practical effectiveness of our technique. Additionally, our empirical results show a significant improvement in fault detection sensitivity when combining expression coverage with du-path analysis. These findings highlight the value of specification-based, data-flow-aware testing for uncovering subtle yet impactful faults in complex systems.
Full text 90,929 characters · extracted from oa-pdf · 6 sections · click to expand

Abstract

In stateful transactional services, latent internal errors often fail to manifest at the output level, eluding traditional black-box testing methods. This phenomenon, known as Failed Error Propagation (FEP), represents a critical blind spot in software quality assurance, particularly in business-critical domains such as financial trading systems. In this paper, we present a novel specification-based testing approach for FEP detection using high-level functional specifications written in Haskell. Our method integrates definition-use (du) path coverage analysis into the test generation process and leverages search-based techniques to synthesize test suites that increase the probability of exposing FEP. We evaluate our approach on a production-grade stock exchange order matching engine and uncover two previously undetected FEP-related bugs, demonstrating the practical effectiveness of our technique. Additionally, our empirical results show a significant improvement in fault detection sensitivity when combining expression coverage with du-path analysis. These findings highlight the value of specification-based, data-flow- aware testing for uncovering subtle yet impactful faults in complex systems. 1 Received: Added at production Revised: Added at production Accepted: Added at production DOI: xxx/xxxx A R T I C L E T Y P E Using Functional Specification to Detect Failed Error Propagation in Stateful Services: An Industrial Case-Study Arvin Zakeriyan Ramtin Khosravi School of Electrical and Computer Engineering, University of Tehran, Tehran, Iran Correspondence Ramtin Khosravi, School of Electrical and Computer Engineering, University College of Engineering, University of Tehran, N. Kargar Ave., Tehran, Iran. Email: [email protected]

Abstract

In stateful transactional services, latent internal errors often fail to manifest at the output level, eluding tradi- tional black-box testing methods. This phenomenon, known as Failed Error Propagation(FEP), represents a critical blind spot in software quality assurance, particularly in business-critical domains such as financial trading systems. In this paper, we present a novel specification-based testing approach for FEP detection using high-level functional specifications written in Haskell. Our method integrates definition-use (du) path coverage analysis into the test generation process and leverages search-based techniques to synthesize test suites that increase the probability of exposing FEP. We evaluate our approach on a production-grade stock exchange order matching engine and uncover two previously undetected FEP-related bugs, demonstrating the practical effectiveness of our technique. Additionally, our empirical results show a significant improvement in fault detection sensitivity when combining expression coverage with du-path analysis. These findings highlight the value of specification- based, data-flow-aware testing for uncovering subtle yet impactful faults in complex systems. K E Y W O R D S Automatic Test Case Generation, Specification-Based Testing, Functional Languages, Search-Based Software Testing, Failed Error propagation 1 INTRODUCTION1 In many industries, software systems have evolved into the backbone of business-critical operations. This is particularly evident2 in domains such as finance, telecommunications, and healthcare, where correctness, reliability, and timeliness are especially3 important.Failures in these domains are rarely negligible: unnoticed data corruption or subtle logic errors can result in financial4 loss, reputational damage, or regulatory non-compliance. Given these stakes, testing plays a critical role in ensuring the5 quality of such systems. One such example is stock exchange platforms, where software systems must not only maintain strict6 correctness guarantees but also meet high levels of non-functional requirements. These properties necessitate the integration of7 non-domain elements into the system, ranging from frameworks and libraries at the architectural level to micro-optimizations at8 the implementation level. Although these non-domain elements are added to improve quality-related features, their presence and9 interaction can introduce new execution paths that may produce incorrect outputs.10 In testing industrial-grade transactional services, black-box model-based testing can be highly effective. Rather than relying on11 access to implementation internals—which are often complex (due to architectural patterns like inversion of control to increase12 testability and dynamic configuration) or partially unavailable (due to third-party libraries and frameworks)—model-based13 approaches focus on high-level specifications of the system’s intended behavior. These specifications serve both as reference14 models from which test cases can be automatically generated and as oracles to determine the expected outputs.15 In our previous work 1, we developed such a framework using Haskell as the specification language. By leveraging the16 expressive power of functional programming and strong typing, we were able to model complex stateful behavior in a concise17 and analyzable manner. Our test case generation approach treated the system under test (SUT) as a function of the form: Request18 Journal 2023;00:1–21 wileyonlinelibrary.com/journal/ © 2023 Copyright Holder Name 1 2 ZAKERIY ANET AL . × State → Response × State, where the state encapsulates business-critical data such as order books, shareholder stock positions,19 and broker credits. Using search-based techniques and structural coverage metrics on the specification, we generated test suites20 that explored diverse execution paths through the SUT and achieved high structural coverage scores.21 However, despite achieving high coverage, we encountered a significant limitation: some faults in the SUT did not manifest in22 its observable behavior. That is, although an fault occurred during computation and corrupted the internal state, the system still23 produced outputs that were syntactically and semantically correct from the external observer’s perspective. Such faults could24 be observed if the internal state were logged after each request execution; however, this is uncommon due to performance and25 security concerns. Consequently, these types of faults remain hidden. This phenomenon, known asFailed Error Propagation26 (FEP), represents a critical blind spot in conventional black-box testing techniques.27 FEP is particularly problematic in transactional stateful services, where errors in one operation can silently propagate into the28 internal state and later affect the outcomes of subsequent operations. For example, a miscalculation when updating a broker’s29 credit might not lead to immediate failure if the resulting value remains within acceptable bounds. However, over time, such30 errors can accumulate and cause serious violations of business rules. In traditional single-request test cases, these issues may31 never surface, as the incorrect state remains latent and unused.32 In 2, the authors show that the frequency of FEP is substantially higher in system-level testing than in unit-level testing. This is33 because detecting FEP using black-box testing is challenging for several reasons. First, many internal state variables are only34 indirectly reflected in system outputs, and only under specific input conditions. Second, the manifestation of an error often35 depends on complex sequences of state transitions, which are unlikely to be triggered by tests based on isolated inputs.36 To overcome these challenges, we extend our previous work 1 and propose a novel approach that integrates data-flow37 coverage—particularly definition-use (du) path coverage—of the specification into the test generation process. Our key insight is38 that FEP can be more effectively detected by focusing on how internal state variables are modified and subsequently used across39 execution paths. By tracing du-paths over state variables within the specification, we can generate tests that explicitly exercise40 these paths, increasing the likelihood of surfacing hidden errors.41 Moreover, we use a test case model that supports sequences of operations, allowing a single test case to contain multiple42 requests. This design decision is crucial: in many cases, the use of a corrupted state variable occurs in a later operation, not43 the one that introduced the fault. By allowing stateful chains of requests in a single test case, we enable the accumulation and44 exposure of errors that would otherwise go undetected.45 Our contributions in this paper can be summarized as follows:46 • We identify the problem of FEP in stateful transactional services and highlight its implications for black-box testing.47 • We introduce a semi-automated data-flow analysis mechanism tailored for high-level functional specifications written in48 Haskell. This mechanism extracts du-paths for internal state variables and supports execution-time instrumentation for49 coverage analysis.50 • We propose a test generation framework that incorporates du-path coverage as a fitness objective, and we explore both total51 and unique path coverage metrics.52 • We evaluate our approach on a production-grade stock market order matching engine. Despite the system’s maturity and53 extensive test coverage, our method detected previously unknown faults that had evaded traditional testing.54 The remainder of this paper is organized as follows. In Section 2, we present the background and provide an overview of the55 system under test. Section 3 details our FEP-aware test generation strategy. Section 4 details the test case generation approach.56 Section 5 reports the results of our empirical study. In Section 6, we discuss related work, and Section 7 concludes the paper.57 2 BACKGROUND58 2.1 Introduction to the Matching Engine59 This work focuses on testing an industrial-grade stock market order matching engine,‡ which forms the core of the electronic60 trading platform used by the Tehran Stock Exchange. The matching engine is responsible for processing and matching various61 ‡ https://en.wikipedia.org/wiki/Order_matching_system USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 3 Order Book Before Matching Buy Sell ID Price Qty ID Price Qty 1 50 500 4 55 500 2 40 1000 5 60 300 3 40 800 6 70 1000 Order Book After Matching Buy Sell ID Price Qty ID Price Qty 7 60 400 6 70 1000 1 50 500 2 40 1000 3 40 800 Input Buy Order ID Price Qty Broker ID Shareholder ID 7 60 1200 112 921 Output T rades BID SID Price Qty 7 4 55 500 7 5 60 300 F I G U R E 1 Example of matching a limit order. Buy order ID 7 is matched with two sell orders. The unmatched remainder is stored in the buy queue. types of trading orders (e.g., Limit §, Iceberg¶, Cross#) and supports multiple qualifiers such as Fill and Kill (also known as62 Immediate or Cancel∥) and Minimum Quantity∗∗.63 In addition to matching, the system performs critical pre- and post-matching checks, including validation of broker credit64 limits and shareholder position thresholds.65 Like other high-performance matching engines, the system is subject to strict non-functional requirements—particularly ultra-66 low latency, where each request must be processed within sub-millisecond timeframes. Meeting these requirements necessitates a67 sophisticated software architecture, combining advanced design decisions, performance optimizations, and the use of specialized68 frameworks and libraries. For instance, the system leverages the Disruptor framework†† for high-throughput event processing,69 Project Lombok‡‡ for reducing boilerplate code, and the Spring framework for dependency injection and configurability.70 To avoid the latency introduced by interactions with external databases, the system uses custom in-memory data structures to71 manage broker credits, shareholder positions, and order books. Alongside architectural optimizations, the codebase incorporates72 low-level performance tuning to ensure responsiveness and scalability under high load.73 2.1.1 Running Example74 To assist readers unfamiliar with trading systems, we introduce a simplified matching algorithm that serves as a running example75 throughout this paper. One of the most common order types is the limit order, defined as: “An order to buy or sell a stock at a76 specific price or better”.§§ A limit buy order can only be matched with sell orders at the same or lower price, while a limit sell77 order can only be matched with buy orders at the same or higher price.78 Each limit order contains key attributes: an order ID, stock symbol, limit price, quantity, and the identifiers of the shareholder79 and broker. If no match is immediately available, the order is stored in anorder book—a data structure containing two priority80 queues: one for buy orders and one for sell orders. Buy orders are prioritized by highest price first; sell orders by lowest price81 first. For orders with equal prices, earlier entries take priority.82 Figure 1 presents a concrete example. A new buy order (ID 7) with a limit price of 60 and quantity of 1200 arrives. The order’s83 broker ID is 112 and it belongs to shareholder 921. The engine attempts to match it with existing sell orders. The first two sell84 orders (ID 4 at 55 and ID 5 at 60) satisfy the price constraint and are partially matched. The third sell order (ID 6 at 70) exceeds85 the limit price and is not matched. The unmatched quantity (400 units) from the new order is then added to the buy queue.86 The matching engine maintains an internal state comprising broker credits, shareholder stock positions, order books, and87 metadata for each stock symbol. When a new order is submitted, several pre-trade validations are executed. For instance, the88 system verifies whether the submitting broker has sufficient credit to cover the order value (price× quantity). In the example,89 upon placing the new order, the system checks that broker with ID 112 has more than 72,000 in credit.90 § https://www.investopedia.com/terms/l/limitorder.asp ¶ https://www.investopedia.com/terms/i/icebergorder.asp # https://www.investopedia.com/terms/c/cross.asp ∥ https://www.investopedia.com/terms/i/immediateorcancel.asp ∗∗ https://www.netsuite.com/portal/resource/articles/inventory-management/minimum-order-quantity-moq.shtml †† https://lmax-exchange.github.io/disruptor/disruptor.html ‡‡ https://projectlombok.org §§ https://www.sec.gov/fast-answers/answerslimithtm.html 4 ZAKERIY ANET AL . Post-trade, the internal state is updated accordingly: broker credits are debited or credited, and shareholder positions are91 adjusted to reflect the new ownership after each executed trade. In our example, the ownership position of shareholder 92192 increases by 800—the total matched quantity. Additionally, the credits of the brokers who placed orders 4 and 5 are increased93 based on the value of the trades they participated in. The credit of broker 112 is decreased by 400 × 60, reflecting the value of94 the queued order.95 2.2 Overview of Our Testing Approach96 In prior work1, we introduced an automatic test case generation framework based on a functional specification of the SUT written97 in Haskell. We modeled the domain logic for stateful transactional services in form of handlers and decorators. The handler has98 the responsibility of handling the each request. It has the general form of Request × State → Response × State. In this model,99 the handler function operates on the new request and based on the internal state and the new request generates the observable100 response and updates the internal state. As each request may require some pre-or post-check and calculation to happen during101 it’s process, we use a recursive pattern similar to the Decorator design pattern. A decorator is a higher order function of type102 Decorator = Handler → Handler which encapsulate the inner handler which in turn may be composed of multiple decorators103 applied to a request handler. A detailed sample of the handler and decorator functions is presented in appendix A.104 To facilitate this specification, we recommend modeling the internal state using domain-appropriate data structures. These105 components are then unified into a composite data structure representing the system state. The Haskell specification not only106 captures the functional behavior of each operation but also enables automatic derivation of test oracles and guides the test107 generation process using search-based algorithms.108 Our framework uses this specification to synthesize black-box test cases that explore meaningful execution paths and validate109 correctness through functional assertions. However, as discussed later in the paper, some faults—especially those related to silent110 state corruption—can evade detection through conventional output-based validation, motivating the work presented in this paper.111 3 TESTING FOR FAILED ERROR PROPAGATION112 The challenge of FEP has been a persistent concern in software testing, particularly in the context of transactional stateful services113 where undetected errors can lead to significant operational failures. There has been much research focusing on measuring the114 likelihood of FEP occurring in a system or on evaluating the strength of tests to capture FEP. In this work, we choose a different115 approach: we incorporate the possibility of FEP directly into our test case generation mechanism and aim to generate tests that116 better detect FEP in the system.117 3.1 The Challenge of Failed Error Propagation118 FEP poses a significant and persistent challenge in software testing, especially for transactional stateful services. In such systems,119 latent internal faults can remain undetected, failing to manifest as observable errors in the system’s outputs. This lack of error120 propagation can lead to severe operational failures, as the underlying corruption can accumulate and eventually trigger critical121 issues. The consequences of FEP are especially pronounced in business-critical domains such as financial platforms, where even122 seemingly minor data inconsistencies can have substantial and harmful repercussions. For instance, FEP in a banking system123 might result in an unnoticed incorrect account balance, while in a stock market matching engine, it could lead to an incorrect124 calculation of a shareholder’s position.125 To illustrate the concept of FEP, consider the Haskell code snippet presented in Listing 1. This simplified example demonstrates126 how a subtle internal fault can remain hidden from typical black-box testing. In this example, we assume there is only one broker127 whose credit is stored in the credit field of the state. The function match simulates processing an order. After matching128 the order against the order book, it first checks if the buyer has sufficient credit usinghasEnoughCredit, then updates the129 credit variable in the state using decreaseCredit. However, the decreaseCredit function contains a subtle bug: it130 intentionally deducts an extra 10 units from the credit. This is not a crash or an immediately obvious error but a silent data131 corruption. The main function then executes a single test case: applying a buy order of price 5 and quantity 10 against an132 order book which contains enough sell orders such that the new order is matched completely, starting with an initial credit of133 USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 5 100. Despite the presence of the bug in decreaseCredit, this specific test case fails to reveal the fault. The test execution134 first checks if there is enough credit considering the new order which there is. After this checking the credit is updated to an135 incorrect value.But since this value is not used from this point on and the credit values are note present in the final outputs of136 the system, this fault can not be discovered. Thus, the test reports a set of trades, masking the underlying data corruption. This137 highlights a key limitation of black-box testing: it often lacks visibility into the system’s internal state, making the detection of138 non-propagating errors difficult.139 Listing 1 Sample Failed Error Propagation Program 1 type Credit = Int140 2141 3 data State = State142 4 { credit :: Credit143 5 -- other parts of the state such as order book144 6 }145 7 deriving (Show, Eq)146 8147 9 initialState :: State148 10 initialState = State { credit = 100 } -- the rest of definition of initial state149 11150 12 data Order = Order151 13 { price :: Int,152 14 qty :: Int153 15 }154 16 deriving (Show, Eq)155 17156 18 data Trade = Trade157 19 { price :: Int,158 20 qty :: Int159 21 }160 22 deriving (Show, Eq)161 23162 24 -- Function to calculate the worth of a trade163 25 worth :: Trade -> Int164 26 worth trade = price trade * qty trade165 27166 28 hasEnoughCredit :: State -> Int -> Bool167 29 hasEnoughCredit state worth = credit state >= worth168 30169 31 decreaseCredit :: State -> Int -> State170 32 decreaseCredit state worth = state { credit = credit state - worth - 10 }171 33172 34 match :: Order -> State -> (Maybe [Trade], State)173 35 match order state =174 36 let trades = [] -- match order against the orderbook and create the list of possible trades175 37 tradesWorth = sum $ map worth trades176 38 in if hasEnoughCredit state tradesWorth177 39 then (Just trades, decreaseCredit state tradesWorth)178 40 else (Nothing, state)179 41 -- the rest of matching validation and post checkings180 42181 43 checkMatchResult :: (Maybe [Trade], State) -> String182 44 checkMatchResult (Nothing, _) = "No trades occurred"183 45 checkMatchResult (Just [], _) = "No trades occurred (empty list)"184 46 checkMatchResult (Just trades, _) = "Trades occurred: " ++ show trades185 47186 48 main :: IO ()187 49 main = do188 50 let buyOrder = Order 5 10189 51 let (trades, updatedState) = match buyOrder initialState190 52 putStrLn (checkMatchResult (trades, updatedState))191 Existing research on FEP has largely concentrated on assessing and predicting the likelihood of FEP occurring within a192 system, rather than on generating tests designed to capture it. One significant branch of this work employs techniques rooted in193 information theory and static code analysis. Here, researchers develop metrics and models to estimate the probability of FEP194 6 ZAKERIY ANET AL . based on the system’s structure, complexity, and potential for information loss during error propagation. The goal is to identify195 system characteristics or code patterns that might inherently make it more susceptible to internal faults not manifesting externally.196 Another line of research frames FEP from the perspective of fault localization in the context of code coverage. In this view,197 FEP is considered an undesired state where a fault exists but is not revealed by the executed test cases, leading to low correlation198 between code coverage and fault detection. These studies often analyze how different coverage criteria (e.g., statement coverage,199 branch coverage) perform in revealing faults that lead to FEP. The focus is on understanding why certain faults remain hidden200 despite achieving seemingly adequate code coverage and on improving fault localization techniques to better pinpoint the source201 of such non-propagating errors.202 Our research aims to address the challenge of FEP from a novel perspective. We focus on black-box testing of stateful203 transactional services, where test cases are automatically generated from specifications implemented in a high-level programming204 language like Haskell. Our approach incorporates specific strategies into the test case generation process to enhance the205 probability of detecting FEP. Specifically, we leverage metaheuristic search algorithms for test case generation and integrate206 coverage criteria designed to improve FEP detection into the fitness functions guiding the search. By doing so, we aim to207 generate test suites with a significantly higher likelihood of uncovering subtle, non-propagating errors within the SUT.208 3.2 Leveraging Data Flow Coverage for Test Case Generation209 In the context of transactional stateful services, we can model services as functions that transform a system’s state based on210 an input request, producing a response and a potentially modified state: Request × State → Response × State.211 FEP in such systems occurs when an internal fault originating from the Request or the processing logic (represented by the212 left-hand side of the function signature) leads to an incorrect alteration of the State, yet this error does not manifest in the213 observable Response. To enhance the likelihood of detecting such FEP scenarios through testing, our strategy should prioritize214 the examination of how variables within the State are manipulated and utilized.215 Among the various test coverage criteria, data flow coverage seems particularly effective for capturing FEP. This effectiveness216 stems from the fact that FEP often arises from subtle discrepancies in the updating of state variables that do not immediately217 propagate to the observable outputs. Data flow coverage criteria, such as du-path coverage, specifically focus on tracing the flow218 of data through the system. A du-path connects the point where a variable is defined (assigned a value) to the points where that219 value is subsequently used. By ensuring coverage of these paths that originate from the beginning of program execution and220 extend to a termination or output point, we can effectively track how changes to state variables propagate (or fail to propagate) to221 the system’s observable behavior during test execution. In essence, by directing our test generation efforts towards covering the222 definition and subsequent uses of state variables, we increase the probability of detecting inconsistencies or errors in how these223 variables are updated and utilized, ultimately improving our chances of capturing FEP. For instance, by meticulously tracking the224 du-paths of the credit variable — from where it is modified during a transaction to where its value influences a later decision225 or output — we can detect if the erroneous –10 unit adjustment has occurred, even if the final transaction appears superficially226 successful. In contrast, simpler criteria like branch coverage might only ensure that all conditional branches in the code are227 executed, but they may not adequately cover the data dependencies and the flow of state variables that are crucial for detecting228 FEP.229 Furthermore, the utilization of functional specifications implemented in a high-level programming language plays a crucial230 role in generating effective FEP-detecting test cases. These specifications provide a clear and abstract representation of the231 application’s intended functional behavior, which simplifies the identification of key internal state variables. This is in contrast to232 the implementation level, where state variables might be fragmented into multiple concrete variables and their modifications might233 be scattered across various execution paths influenced by non-functional requirements or programming language constraints that234 are not directly derived from the core functional requirements. By generating tests from a high-level specification that explicitly235 defines the state and its transformations, we can create test cases that are more directly targeted at scrutinizing the intended236 behavior of these critical state variables and their potential for contributing to FEP.237 3.2.1 Challenges in Defining Data Flow Analysis for Haskell Specifications238 Generating accurate data flow information for Haskell programs presents substantial hurdles due to the language’s inherent239 characteristics, notably purity, immutability, and lazy evaluation. Unlike imperative programming paradigms where data flow is240 USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 7 often tightly coupled with explicit control flow structures, Haskell’s functional nature and its strategy of deferred computation241 significantly complicate the construction of precise data flow graphs. Some of these difficulties echo those encountered in the242 analysis of other functional languages like Erlang and Scheme, as highlighted in the works of Widera and Zeller3 and Shivers4.243 These authors underscore the complexity arising from features such as higher-order functions, recursive definitions, and the244 absence of explicit sequential control flow instructions. They note that extracting the flow graph without considering the data245 flow is impossible, as the higher-order functions present themselves as expressions in early iterations of flow graph generation.246 While some prior research has explored the generation of data flow information for Haskell programs, to the best of our247 knowledge at the time of writing, a fully automatic tool for generating comprehensive data flow graphs and subsequently248 extracting du-paths directly from Haskell code was not readily available. As an initial exploration, we attempted to implement a249 tool aimed at measuring du-path coverage during program execution. Our tool’s design involved two main components: a static250 analysis phase that parsed the Haskell source code to construct its Abstract Syntax Tree (AST), and an annotation phase that251 instrumented the program to record a detailed execution trace at the expression level during runtime. By analyzing this runtime252 trace in conjunction with the program’s AST, we aimed to identify the specific du-paths that were exercised during a particular253 execution. We also intended to statically extract all potential du-paths from the AST to calculate a coverage score based on the254 executed paths.255 However, this initial attempt encountered a significant obstacle due to Haskell’s lazy evaluation strategy. The deferred256 evaluation resulted in a non-trivial and often discontinuous execution trace, characterized by jumps and out-of-order evaluation257 of expressions. This made it exceedingly difficult to reconstruct consistent and meaningful du-paths from the runtime data.258 Consider the illustrative example presented in Listing 2 and its corresponding flow graph in Figure 2. The flow graph is manually259 generated based on the work of 3.260 Listing 2 The impact of lazy evaluation on data flow extrac- tion for Haskell programs 1 getMax :: Int -> Int -> Int 2 getMax a b 3 | a > b = a 4 | otherwise = b 5 6 ampilfy :: Int -> Int -> Int 7 amplify c d 8 | d Int -> Int 12 f a b = amplify c 4 13 where 14 c = getMax a b f: import(Arg1, Arg2) context() return res1res1 = amplify c 4 getMax:import(Arg3, Arg4) context() Arg3, Arg4 Arg3 > Arg4 Arg3 ≤ Arg4 ret2 = Arg3 ret2 = Arg4 c = getMax Arg1 Arg2 amplify:import(Arg5, Arg6) context() Arg6 Arg6 < 5 Arg6 ≥ 5 ret3 = 2 × Arg5 ret3 = 3 × Arg5 return ret3 return ret2 1 4 2 3 5 6 3 6 F I G U R E 2 The statically generated flow graph of example Listing 2 in the style of3. 8 ZAKERIY ANET AL . F I G U R E 3 Encapsulating the state with reader and writer functions to control the access to the state As the static creation of the flow graph shows, in the execution of function f, first a call to getMax function should be261 made and then the return value of that function should be passed to the amplify function to create the f → getMax →262 amplify trace. The authors of3 introduced the concept of du-chains to address the problem of immutable variables and we263 used the same notion for creation of the flow graph. So the definitions of variables a and b are considered in line 2 and then they264 pass through getMax function where one of them is passed as the value to amplify function and then it is used to calculate265 the return value of it. So the expected execution trace is 1 → 2 → 3 → 4 → 5 → 6. Note that on each execution, one266 of the nodes marked as 3 and 6 is executed based on the input values. But the runtime execution trace generates the flow f267 → amplify → getMax → the rest of amplify (1 → 4 → 5 → 6 → 3 → 6). This jump in execution268 happens because the return value of getMax function is not calculated until it is needed. So the call to getMax function269 happens in the middle of execution of amplify function and the generated execution trace is different from the statically270 generated one shown in Figure 2.271 Given that the automatic generation of comprehensive data flow analysis information was a complex undertaking and272 represented only a part of the broader research objectives, and considering the limited time available, we made the decision273 to remove the automatic data flow generation component from the immediate scope of this paper. We intend to address this274 challenging aspect in more detail in a separate research. For the purposes of this paper, we adopted a semi-automatic approach to275 extract du-paths for key state variables as a proof of concept, acknowledging the limitations and manual effort involved.276 3.2.2 Semi-Automatic Data Flow Analysis Using Monads277 In our prior work 1, we developed a test coverage calculation mechanism leveraging the monadic structure of our specification.278 This mechanism strategically embedded instrumentation points within monadic computations to collect and output coverage279 data during execution. For this work, we extended that mechanism to capture data relevant to state variable data flow analysis in280 our Haskell specification.281 Our approach encapsulates direct access to the system’s state components via intermediary accessor and mutator functions.282 These controlled interfaces not only perform the intended state manipulations but also update the data flow trace within the monad.283 Figure 3 presents the diagram for using monads as wrapper for accessor method in state variable. The appendix B illustrates this284 design pattern: the function signatures evolve from State → AType → . . . → State to State → AType → . . . →285 Coverage State , where Coverage is a monad that wraps state operations to record data flow events.286 For example, consider the code snippet in Listing 3, extracted from our matching engine specification. Here, broker credit287 balances are updated based on transaction value. The updateCredit and getCredit functions serve as encapsulated inter-288 faces for modifying and retrieving credit information, respectively. These functions are instrumented to record definition points289 (e.g., "DF-D-credit-" ++ show bid ) and usage points (e.g., "DF-U-credit-" ++ show bid ), corresponding to290 the definition-use pairs critical for du-path coverage. By replacing all direct state accesses with calls to these functions, we291 decouple core specification logic from data flow instrumentation, facilitating flexible and modular trace collection. The recorded292 sequences of definition and usage events enable extraction of executed du-paths for coverage analysis.293 USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 9 Listing 3 Defining data flow points using monads in specification 1 updateBuyerCreditByTrade :: MEState -> Trade -> Coverage MEState 2 updateBuyerCreditByTrade state t = 3 updateCredit state bid newCredit 4 where 5 bid = buyerBrId t 6 newCredit = getCredit bid - valueTraded t 7 8 9 updateCredit :: MEState -> BrokerId -> Int -> Coverage MEState 10 updateCredit state bid value = 11 state {creditInfo = Map.insert bid newCredit ci} 12 ‘covers‘ ("DF-D-credit-" ++ show bid) 13 where 14 ci = creditInfo state 15 newCredit = creditInfo state Map.! bid + value 16 -- Assuming ’value’ is the change in credit 17 18 getCredit :: MEState -> BrokerId -> Coverage Int 19 getCredit state bid = ci Map.! bid ‘covers‘ ("DF-U-credit-" ++ show bid) 20 where 21 ci = creditInfo state 294 3.3 The Need for Multiple Requests within a Test Case for Enhanced FEP Detection295 In our earlier work1, incorporating multiple requests within a single test case served primarily to model diverse initial states296 for the specification, thereby streamlining the test case generation process. However, for the specific goal of uncovering FEP,297 executing a sequence of multiple requests within the same continuous execution context becomes critically important.298 As previously discussed, employing data flow coverage to detect FEP necessitates focusing on execution paths that encompass299 a sub-path originating from the definition of a state variable to a subsequent usage of that same variable. For many internal300 state variables, such a complete du-path might not be fully traversed within the execution scope of a single isolated request.301 This limitation arises because, after a state variable is modified by a request, its updated value might not be utilized within the302 remaining execution flow of that same request, thus preventing any fault in the state variable’s modification from manifesting in303 the request’s output.304 However, by executing multiple requests sequentially within a single test case, we can construct execution paths that span305 across request boundaries. Specifically, a state variable might be altered by an error within the processing of one request, and306 its incorrectly modified value might then be used in a subsequent, related request within the same test case. This inter-request307 dependency allows the impact of erroneous state variable calculations to propagate and become observable in the outputs of308 these later requests, thereby significantly enhancing our ability to detect FEP.309 Consider the FEP example illustrated in Listing 1. When each test case is limited to a single request, the data corruption of the310 credit state can not be revealed as the updated value of credit is never used from that point on. In contrast, if a test case contains311 more than one request, there is a du-path that starts from one request and finished in the next request. Generating tests for this312 path increase the chance of discovering the subtle fault possible. If there is more than two requests, considering this execution313 path multiple times makes the discovery of this bug much easier as each execution leads to bigger deviation from the intended314 internal state.315 While the conventional practice for testing transactional systems often involves a single request per test case – a methodology316 that aligns well with the abstract, isolated nature of individual transactions and simplifies the verification of individual test317 outcomes – our approach leverages the specification itself as a test oracle. Because we can readily execute a sequence of requests318 against our Haskell specification and observe the resulting state transitions and outputs for each request in the sequence, the319 complexity of handling and verifying test results for multi-request test cases is significantly mitigated. This capability allows us320 to effectively harness the power of multi-request test cases to improve FEP detection without being unduly burdened by the321 challenges typically associated with verifying such complex test scenarios in traditional testing frameworks.322 10 ZAKERIY ANET AL . 4 TEST CASE GENERATION323 The primary objective of our approach is to generate an effective test suite for a SUT based on a formal specification. Given324 that we employ a metaheuristic approach, specifically a genetic algorithm, for test case generation, the design of robust fitness325 functions is paramount. This section details the metaheuristic approach we utilized and the various fitness functions we proposed326 to optimize test suite generation, particularly for capturing FEP.327 4.1 Multi-Objective Test Case Generation Approach328 We adopted a genetic algorithm as our metaheuristic search strategy for test case generation. While this approach builds upon329 the conceptual framework established in our previous work, it required a significantly refined implementation of the genetic330 algorithm itself. In our earlier research, our genetic algorithm aimed to optimize two primary objectives: minimizing the size331 of the generated test suite and maximizing the coverage achieved over the formal specification. These two objectives were332 combined into a single fitness value using a weighted linear formula. However, subsequent in-depth analysis revealed a critical333

Limitation

the assigned weights to these objectives had a disproportionately large and often unpredictable impact on the genetic334 algorithm’s search behavior. Given the inherent interdependency between test suite size and specification coverage, even small335 adjustments to the weights could lead to substantial and difficult-to-predict changes in the algorithm’s performance, making the336 determination of optimal weight values a significant challenge.337 To overcome this limitation and enable a more robust and principled optimization process, we transitioned to employing a338 multi-objective genetic algorithm. This paradigm allows for the simultaneous optimization of multiple potentially conflicting339

Objectives

without the need to manually define and tune arbitrary weightings. Specifically, we utilized the Non-dominated340 Sorting Genetic Algorithm II (NSGA-II), an established and widely used multi-objective evolutionary algorithm. We employed341 an off-the-shelf implementation of NSGA-II readily available from the Pymoo library ¶¶. NSGA-II operates based on the342 principle of non-dominated sorting, where individuals (in our case, candidate test suites) are ranked according to their dominance343 relationships. Solutions that are not dominated by any other solution in the population are prioritized, effectively guiding the344 search towards the Pareto frontier – a set of optimal solutions representing the best possible trade-offs between the different345 objectives. For the crossover operation, we utilized the Simulated Binary Crossover (SBX) implementation provided by the346 Pymoo library. SBX is particularly well-suited for problems with continuous variables, as it effectively explores the range of347 possible values between parent solutions, facilitating a more comprehensive search of the solution space. Similarly, for the348 mutation operator, we employed the Polynomial Mutation implementation from Pymoo. Polynomial mutation is designed to349 efficiently explore the valid ranges of the problem variables, introducing small random changes to the offspring to maintain350 diversity within the population and prevent premature convergence.351 Through the iterative processes of non-dominated sorting-based selection, SBX crossover, and polynomial mutation, NSGA-352 II effectively navigates the multi-dimensional objective space, identifying a diverse set of non-dominated test suites. These353 solutions represent a range of optimal trade-offs between the defined objectives, offering a comprehensive and balanced coverage354 of the specification while also considering the size of the generated test suite.355 4.1.1 Problem Formulation356 We adopted a problem formulation for our genetic algorithm that closely mirrors our previous work 1. To ensure this paper357 remains self-contained, we provide a concise overview of this formulation here. A key design principle of our test case generation358 approach is that the fitness of a candidate solution is evaluated over theentire test suite it represents, rather than on individual359 test cases in isolation. This idea was first presented in5. The holistic evaluation strategy is crucial for mitigating issues such as360 accidental coverage. Our initial exploratory experiments, where fitness was assessed at the individual test case level, revealed a361 tendency for simpler coverage metrics like expression coverage to become dominant early in the evolutionary process. This362 dominance often led to “accidental coverage” of other parts of the specification without the test cases being specifically designed363 to target those areas, rendering the optimization of other important objectives ineffective.364 ¶¶ https://pymoo.org/algorithms/moo/nsga2.html USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 11 TC 1 TC 2 … TC 40 Flag Fixture Orders Credits Shares Ref Price Order 1 Order 2 … Order 10 Shares 1 Shares 2 … Shares 10 Order ID Broker Shareholder Price �uantity Disclosed �uantityFill and KillMin �uantitySide F I G U R E 4 The structure of a chromosome representing a test suite Figure 4 illustrates the structure of a single chromosome within our genetic algorithm, where each chromosome represents a365 complete test suite. A test suite is composed of a variable number of test cases, up to a predefined maximum.366 • Flag: specifies the presence of a test case in the final test suite. With this flag, the search-based algorithm can dynamically367 manage the number of test cases in the test suite.368 • Fixture: defines the initial state of the system before the execution of the associated Orders. This initial state includes369 parameters such as the initial credit balance for each broker, the initial ownership quantities of shares for each shareholder,370 and a reference price for the security being traded. This reference price serves to define valid ranges for order prices during371 test case generation.372 • Order: is comprised of parameters specific to different types of trading orders, along with a unique Order ID. This Order ID373 provides a mechanism to selectively exclude specific orders from a test suite during the evolutionary process, allowing the374 algorithm to explore solutions with varying numbers of orders, potentially fewer than the maximum allowed per test case.375 As previously discussed in Section 3.3, the inclusion of multiple requests within a single test case is a deliberate design choice.376 This facilitates the modeling of more complex, real-world scenarios where sequences of requests are common and, crucially,377 enhances our ability to detect FEP.378 4.2 Fitness Function379 The fitness function plays a crucial role in guiding the search process of our metaheuristic test case generation approach. In the380 context of evolutionary algorithms like genetic algorithms, the fitness function evaluates the quality of each candidate solution381 (in our case, a test suite), providing a measure that the algorithm aims to optimize. While our approach leverages the formal382 specification to compute these fitness values, our ultimate goal is to generate effective test cases for the SUT. We acknowledge383 that the relationship between specification coverage and SUT coverage is not always straightforward, necessitating careful384 consideration in our fitness function design.385 Our previous work indicated thatexpression coverage of the specification yielded the most effective test suites when evaluated386 against the SUT. Expression coverage is a source code coverage metric that considers every expression at any level of the abstract387 syntax tree for execution during testing. Building upon this prior finding, we utilize expression coverage as a baseline objective in388 our multi-objective fitness functions, particularly for capturing aspects related to control flow coverage within the specification.389 As highlighted in subsection 3.2, data flow coverage is theoretically well-suited for detecting FEP, as FEP often arises from390 issues in how state variables are defined and subsequently used. To incorporate data flow considerations into our fitness function,391 we explored two distinct methods for quantifying the data flow exercised by a test suite within the specification:392 1. Total Def-Use Path Count: This method calculates the cumulative number of def-use paths that are executed at least once393 across all test cases within the generated test suite. It aims to maximize the overall exercise of data dependencies, without394 explicitly penalizing the repeated execution of the same paths. The underlying assumption is that a higher total count of395 def-use path executions involving internal state variables increases the probability of triggering and observing FEP.396 12 ZAKERIY ANET AL . 2. Unique Def-Use Path Count: This method, in contrast, focuses on the diversity of the data flow coverage. It counts only397 the number of unique def-use paths that are executed by the test suite, regardless of how many times each unique path is398 traversed. The rationale behind this approach is that exploring a wider variety of data flow relationships involving internal399 state variables, by covering different def-use paths, might lead to a more effective detection of FEP by exposing faults under400 different execution scenarios.401 Based on these two distinct ways of measuring data flow coverage, we proposed and evaluated five different fitness functions402 for our test case generation process:403 1. Expression Coverage: This serves as our baseline fitness objective, allowing us to compare the performance of our data404 flow-oriented fitness functions against a previously established effective coverage metric. Due to changes in the genetic405 algorithm implementation, we could not directly reuse the quantitative results from our prior work, necessitating this406 baseline comparison.407 2. Du-Path Count: This fitness function directly utilizes the first data flow measurement method, aiming to maximize the408 total number of def-use paths executed within the specification by the generated test suite.409 3. Du-Unique Paths: This fitness function directly utilizes the second data flow measurement method, aiming to maximize410 the number of unique def-use paths executed within the specification by the generated test suite, prioritizing diversity in411 data flow coverage.412 4. Expression Coverage + Du-Path Count: This is a multi-objective fitness function that simultaneously optimizes two413

Objectives

the expression coverage of the specification and the total count of def-use paths executed. By leveraging the414 capabilities of NSGA-II, we aim to find a balance between achieving good structural coverage (via expression coverage)415 and maximizing the exercise of data dependencies relevant to FEP.416 5. Expression Coverage + Du-Unique Paths: This is another multi-objective fitness function that simultaneously optimizes417 expression coverage of the specification and the number of unique def-use paths executed. Similar to the previous multi-418

Objective

function, this aims to balance structural coverage with the diversity of data flow coverage, hypothesizing that this419 combination will lead to more effective FEP detection in the SUT.420 We introduced the multi-objective fitness functions (options 4 and 5) because we hypothesized that relying solely on data421 flow coverage as the fitness measure for test case generation might not adequately address the structural coverage of the SUT.422 We suspected that optimizing only for data flow could potentially lead to the generation of test suites that effectively target423 data dependencies relevant to FEP but might neglect other important aspects of the SUT’s functionality, resulting in a less424 comprehensive and ultimately less effective test suite overall. Also, our current semi-automatic methodology for extracting425 def-use paths from the specification only considers paths that both originate and terminate at points where internal system state426 variables are defined or used. This limited scope means that the broader context of these du-paths within the overall application427 execution flow is not fully captured. Consequently, optimizing solely for these isolated du-paths can lead to a lack of diversity in428 the explored execution scenarios, which in turn reduces the probability of triggering the specific conditions required to expose429 FEP in the SUT. Therefore, combining data flow coverage with expression coverage aims to create a more balanced and robust430 approach to test case generation.431 5 EV ALUATION432 To rigorously assess the effectiveness of our proposed approach, we selected a real-world implementation of a stock exchange433 matching engine software system as our SUT. This complex system, consisting of approximately 148,000 lines of Java code,434 has undergone substantial testing efforts by its development team, and they were almost confident about the correctness of the435 system. Their existing test infrastructure includes a comprehensive suite of nearly 3,300 unit and Behavior-Driven Development436 (BDD) tests, more than 90 end-to-end integration tests, and a large-scale test suite containing over one million order transactions437 derived from real-world usage data of the currently active legacy system. This extensive legacy data is used to verify various438 aspects of the order processing behavior under realistic load and conditions. The development team’s estimation that testing439 activities consumed over 30% of their total development effort underscores the significant emphasis they placed on ensuring the440 quality and reliability of this critical software system.441 USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 13 Haskell Specification Test Case Generation Algorithm Adapter TestExecution Engine Coverage Measurements NSGA-II Library Test Suite SUT F I G U R E 5 The high level view of our evaluation process including test case generation and execution of test on SUT Despite this impressive level of prior testing, we deliberately chose this mature and seemingly well-tested system as our442 evaluation target. Our rationale was that such a system presents a realistic and challenging scenario for our FEP-focused test443 case generation approach. We hypothesized that even in systems subjected to extensive traditional testing methodologies, subtle444 vulnerabilities related to FEP, particularly those involving the system’s internal state and its evolution across transactions, might445 still persist and remain undetected. Therefore, this sophisticated and actively used stock exchange matching engine provides a446 strong and relevant benchmark against which we can objectively evaluate the potential of our technique to uncover these elusive447 FEP issues.448 To facilitate our evaluation, we developed a formal Haskell specification that models a significant portion of the SUT’s overall449 specification, with a specific focus on the core order processing logic and the management of the system’s internal state during450 trade execution. Furthermore, we engineered a crucial adapter component. This adapter serves as a bridge between our test case451 generation framework and the SUT. Its responsibilities include converting the abstract test suites generated by our Haskell-based452 approach into the specific input format expected by the Java-based matching engine application. Additionally, the adapter is453 responsible for setting up the necessary test environment for each generated test case based on the initial state defined in the test454 case specification. This ensures consistent and reproducible execution of our generated tests against the SUT. Figure 5 provides455 an overview of our evaluation process.456 5.1 Fault Model and Targeted Mutation457 We use mutation-based fault injection for evaluating the effectiveness of a test suite in detecting FEP. To apply fault injection458 in a meaningful way, we first needed to establish a well-defined fault model that specifically targets potential FEP scenarios459 within our SUT. The internal state of the matching engine consists of order book, broker credit and position ownership, and460 security internal variables. A detailed analysis of the SUT’s specification and its observable outputs identified two critical areas461 within the system’s internal state that are particularly susceptible to FEP: broker credit and position (share) ownership. The462 order book and security internal variables are not considered since there are messages in the system output that expose them.463 Credit and position, while not directly exposed as outputs of individual transactions, play a fundamental role in the core order464 processing logic. They directly influence whether incoming orders are accepted or rejected and are updated as a consequence of465 each successful transaction. Our fault model for detecting FEP, therefore, focuses on introducing errors in the calculations and466 comparisons involving these two key aspects of the internal system state.467 To effectively model potential faults that could lead to FEP, we designed and implemented a novel, domain-specific mutation468 operator that we apply to the SUT’s codebase. There is an innate difference between the common mutation operators and469 our mutation operator. Mutation operators commonly generate bugs by changing an operation in the execution as a bug that470 might happen during development, but our mutation operator aggregates all the miscalculations that can happen during the471 execution procedure and simulates it by introducing a ratio-based perturbation to the calculated change in these values. Listing 4472 shows a simplified example of how this mutation is applied to the updateCredit method in the SUT (written in Java). The473 commented-out line represents the original, correct implementation of the credit update. The active line below it shows the474 mutated version, where the calculated amount representing the change in credit is multiplied by a RATIO before being applied475 to the broker’s credit. ThisRATIO serves as an abstraction for a range of potential miscalculations that could occur during credit476 updates, including mathematical errors in the calculation itself or incorrect conditional evaluations that ultimately lead to an477 altered change in the credit as an internal state variable. During our mutation testing experiments, we systematically varied478 14 ZAKERIY ANET AL . the RATIO and recorded the smallest deviation from 1.0 that resulted in a test failure, providing a measure of the test suite’s479 sensitivity to this type of fault.480 The RATIO parameter effectively controls the severity of the injected fault. A RATIO value very close to 1.0 represents a481 small, subtle miscalculation that leads to FEP, as the internal state is slightly corrupted without immediately causing an obvious482 failure. Conversely, a RATIO value further away from 1.0 simulates a larger, more significant miscalculation that is more likely483 to be detected by even basic testing. This tunable nature of our mutation operator allows us to simulate faulty versions of the484 SUT with varying degrees of error subtlety, enabling us to evaluate the test suite’s ability to detect FEP across a spectrum of485 fault severities. By using this ratio-based perturbation, we effectively abstract our fault model, allowing us to systematically486 explore a range of potential FEP scenarios related to miscalculations in critical internal state updates.487 Listing 4 Our mutation operator applied to the updateCredit method in the SUT 1 public void updateCredit(Money amount){488 2 {489 3 // credit = credit.increase(amount);490 4 credit = credit.increase(amount.multiply(RATIO));491 5 }492 5.2 Evaluation Methodology493 To quantify the effectiveness of our proposed test case generation approach, we employed themutation score as our primary494 evaluation metric. Mutation score is a widely accepted measure in software testing that assesses the ability of a test suite to495 detect injected faults (mutants) in the SUT. As highlighted by Jahangirova et al. 2, the characteristics and prevalence of FEP496 can significantly differ when considering system-level testing compared to unit-level testing. Their findings suggest that the497 probability of FEP occurring at the system level is substantially higher than at the unit level, both for real-world faults and498 for artificially injected faults (mutants). This crucial observation implied that the existing unit-level tests developed for our499 SUT were not a suitable baseline for evaluating the effectiveness of our system-level FEP detection strategies. Furthermore, the500 manually developed end-to-end tests, while valuable for integration testing, were also deemed inappropriate as a baseline due to501 their limited coverage of the overall system behaviors.502 Given these considerations, and building upon the findings of our previous work1, we recognized that test suites generated503 using expression coverage of the specification demonstrated superior coverage compared to the existing manually developed504 tests for our SUT. Therefore, to specifically measure the impact of incorporating data flow considerations into our test case505 generation process on the effectiveness of the resulting test suites (particularly for FEP detection), we elected to use test suites506 generated solely based on expression coverage of our Haskell specification as the baseline for our evaluation. It is important to507 note that, as mentioned in subsection 4.2, due to a different implementation of the underlying genetic algorithm in this work508 compared to our previous research, we could not directly utilize the test suites generated in 1. Instead, we generated a new set of509 baseline test suites specifically for this evaluation by optimizing solely for expression coverage of our Haskell specification510 using our current genetic algorithm framework. This ensures a fair and direct comparison with the test suites generated using our511 novel FEP-focused fitness functions.512 5.3 Bug Detection513 A significant outcome of our evaluation was the successful detection of two previously unknown FEP bugs within the real-world514 stock exchange matching engine application (SUT). This is in addition to the instances of bugs that we identified in our earlier515 work on this same system. It is important to note that these bugs cannot be discovered by our previous approach since we focused516 on generating tests that provide maximum expression coverage and, as a result, the generated tests did not focus on including the517 internal state across multiple requests to access for FEP.518 The first bug we uncovered was related to the management of position ownership. Specifically, we found a discrepancy between519 the behavior defined in our Haskell specification and the actual implementation in the SUT concerning the comparison of current520 ownership with maximum allowable ownership limits. According to our specification, if a transaction would result in a broker’s521 position ownership being equal to the maximum allowed value, the transaction should be rejected. However, we discovered that522 USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 15 the SUT implementation incorrectly accepted such transactions, potentially leading to violations of ownership constraints within523 the system. This represents a subtle error in the handling of boundary conditions for a critical internal state variable.524 The second bug we identified pertained to the calculation of commission credit transactions within the SUT. The system525 has a feature to calculate and apply commission credits based on the value of placed orders. The default configuration for the526 system, which was active during the execution of our generated test suites, sets the commission rate to zero. Under this default527 setting, the intended behavior for performance optimization is that no credit transaction for commission should be created.528 Furthermore, there were explicit assertions within the SUT’s code to verify that no credit transaction with a value of zero is ever529 created. However, our generated test cases triggered a specific, less frequently executed path in the order processing logic. In this530 particular code path, a commission credit transaction was always created, regardless of the commission value. Consequently,531 when the commission rate was zero, this path led to the creation of a credit transaction with a zero value, directly violating one532 of the internal assertions designed to prevent such transactions. This scenario highlights how our approach can uncover bugs in533 less common execution paths that might be missed by more traditional testing methods.534 The discovery of these two distinct and non-trivial bugs in a mature and extensively tested real-world application provides535 empirical evidence for the practical utility and effectiveness of our specification-based test case generation approach, particularly536 in its ability to uncover subtle defects, including those related to the management of internal state and potential instances of FEP.537 5.4 Mutation Score Analysis538 To quantitatively evaluate the effectiveness of our approach in detecting injected faults related to FEP, we conducted a series of539 experiments using five different fitness functions to guide the test case generation process:540 1. Expression coverage541 2. Total du-path coverage542 3. Unique du-path coverage543 4. Combined expression and total du-path coverage544 5. Combined expression and unique du-path coverage545 For each of these five fitness functions, we generated 10 independent test suites using our genetic algorithm. To mitigate the546 potential influence of the algorithm’s inherent randomness on the results, we then calculated the average mutation ratio achieved547 by these 10 test suites for each fitness function. It’s important to note that for our mutation testing, we exclusively utilized our548 custom-designed mutation operator (as described in subsubsection 4.1.1), and the average mutation ratios for credit and position549 ownership were measured and are reported separately for each fitness function in Table 1.550 T A B L E 1 Average mutation ratio of each fitness function with regard to credit and position ownership (lower ratio indicates better fault detection). Fitness Function Mutation Ratio Credit Position Ownership Without du (expression coverage) 2.10 2.17 Total du-path 2.07 2.34 Unique du-path 2.24 2.55 Combined with total du-path 1.47 1.46 Combined with unique du-path 1.43 1.66 The results presented in Table 1 clearly indicate that employing a multi-objective fitness function that combines expression551 coverage with du-path coverage (both total and unique) yields significantly better mutation ratios (lower ratios signify more552 effective fault detection) compared to using each criterion in isolation. Test suites generated by optimizing solely for expression553 coverage, while aiming to cover all expressions in the specification, appear to lack sufficient focus on exercising the internal554 system state in ways that are likely to reveal potential FEPs. These test suites might cover the control flow logic well but may not555 adequately explore execution paths that involve multiple interactions with the same state variables across different requests.556 Similarly, simply maximizing either the total count or the number of unique du-paths in isolation also proves to be less557 effective than the combined approaches. There are two primary reasons for this. First, as mentioned in subsection 4.2, our558 du-path coverage method does not capture all the paths of execution scenarios, and as a result, optimizing solely for these559 16 ZAKERIY ANET AL . isolated du-paths can lead to a lack of diversity in the explored execution scenarios, which in turn reduces the probability of560 triggering the specific conditions required to expose FEP in the SUT.561 Second, inherent discrepancies can exist between the execution paths within our high-level Haskell specification and the562 corresponding execution paths in the Java implementation of the SUT. These differences can arise due to various factors such as563 performance optimizations implemented in the SUT or fundamental differences between the two programming paradigms. This564 was not the case when using expression coverage as the fitness function for test case generation, since our results in1 showed565 that using expression coverage can generate test suites that have near perfect branch coverage score. But, achieving high du-path566 coverage in the specification does not guarantee equivalent coverage of relevant data flows in the SUT, and there might be crucial567 execution paths in the SUT that are not directly represented as du-paths in our specification.568 The combined fitness functions effectively address these shortcomings. By simultaneously encouraging the generation of569 test suites that explore a wider range of execution paths (through expression coverage) and specifically focusing on those570 paths that involve interactions with the internal system state (through du-path coverage), we achieve a significantly enhanced571 capability for detecting FEP. Notably, the combined fitness function utilizing total du-path coverage achieved an approximate572 40% improvement in the average mutation ratio compared to the baseline fitness function that only considered expression573 coverage. This substantial improvement underscores the value of integrating data flow considerations into the test case generation574 process for effectively uncovering subtle, state-related bugs like those that can lead to FEP.575 5.5 Evaluating the Impact of Test Case Generation Parameters576 To gain a deeper understanding of how the configuration of our genetic algorithm influences its performance, we conducted577 evaluations focusing on two key parameters that we hypothesized could significantly impact the results.578 F I G U R E 6 Impact of varying test suite size and test case size on mutation ‘RATIO’ (lower is better) F I G U R E 7 Impact of varying test suite size and test case size on execution time of the genetic algorithm The first set of parameters we investigated were the test suite size (the number of test cases in a generated suite) and the test579 case size (the number of requests within a single test case). As mentioned previously, our base size settings for these parameters580 were 40 and 10, respectively. To assess their impact on the quality of the generated test suites, we conducted experiments with581 both larger and smaller values for these sizes. Figure 6 and Figure 7 illustrate the resulting average mutation ‘RATIO’ and582 execution time for these different configurations. The results indicate a trend: increasing both the test case size and the overall583 test suite size generally leads to lower mutation ‘RATIO’ but increases the execution time. Since a lower mutation ‘RATIO’584 signifies a greater ability of the test suite to detect the injected faults (indicating the discovery of more subtle errors), this suggests585 that if the longer execution time can be tolerated, larger test suites with more requests per test case tend to be more effective in586 uncovering FEP. This improvement occurs because larger test suites and longer test cases provide more opportunities to create587 diverse sequences of actions and interactions with the system’s internal state, thus increasing the chances of exercising du-paths588 USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 17 that expose subtle errors. However, it is important to note that increasing these sizes also typically leads to a significant increase589 in the overall execution time required to run the generated test suites against the SUT.590 F I G U R E 8 Impact of varying the initialization space (range of valid parameter values) on mutation ‘RATIO’ (lower is better) F I G U R E 9 Impact of varying the initialization space (range of parameter values) on execution time of the genetic algorithm The second parameter we examined was the range of values for the various parameters within our genetic algorithm’s591 problem formulation. As outlined in subsubsection 4.1.1, each parameter that defines a test case (e.g., order price, quantity,592 initial credit ranges) has a defined range of permissible values. Restricting these ranges effectively reduces the size of the search593 space explored by the genetic algorithm during test case generation. Figure 8 and Figure 9 show the impact of varying these594 value ranges on the average mutation ‘RATIO’ and execution time.595 Our initial intuition was that a smaller state space would be easier to explore, so we set the initial values for base size to a small596 set of values. However, the results generally demonstrate that widening the range of possible values for the test case parameters597 tends to result in lower (better) mutation ‘RATIO’ while not meaningfully impacting the execution time. The occasional slight598 deviations from this pattern are likely due to averaging results across multiple independent runs, where in some specific test599 suites with wider ranges, the random initialization might not have produced individuals that effectively targeted the injected600 faults. Despite these minor fluctuations, the overall trend suggests that a larger, more diverse initial search space is beneficial for601 generating test suites that are effective at detecting FEP. This is because a larger state space offers more flexibility in generating602 the specific input sequences needed to trigger and observe the effects of subtle state-related faults. Consider the FEP example603 in Listing 1 from subsection 3.1. If the initial credit is constrained to a small range (e.g., 10 or less), the fault (the extra 10604 deduction) might only be revealed by generating a sequence of two orders whose combined worth is just under the initial credit.605 However, by allowing a wider range for the initial credit, the genetic algorithm has more opportunities to generate scenarios606 where a single order (or a sequence of orders) leads to the faulty path being taken and the error in the credit update becoming607 observable in subsequent operations or the final state.608 6 RELATED WORK609 Although specification-based test case generation has received considerable attention in the software testing literature, we are610 not aware of any research that directly incorporates the problem of FEP into the test generation process with the explicit goal of611 producing test cases that are more likely to detect FEP. In this section, we provide a structured overview of existing research612 related to FEP and data-flow coverage in functional languages. It should be noted that related work on using functional languages613 for specification-based test case generation has been covered in depth in1, and we encourage the reader to consult that paper for614 further details.615 18 ZAKERIY ANET AL . 6.1 Failed Error Propagation616 FEP is a well-recognized issue in software testing, especially in the context of complex, stateful, and safety-critical systems.617 While terminology and framing may differ across domains, the underlying problem of internal errors that fail to manifest at the618 output has been studied in various contexts. We categorize the literature on FEP into five major areas:619 6.1.0.1 Software Architecture-Level FEP Analysis620 Some research investigates how FEP occurs at the architectural level, focusing on error propagation between software components.621 These studies treat FEP as a reliability issue. For example, Malik et al. 6 propose a method for estimating system reliability622 by statically analyzing component interconnections to compute error propagation probabilities. The estimation is then used623 to identify the components most sensitive to system reliability. Similarly, Abdelmoez et al.7 use state information and inter-624 component messaging to quantify propagation likelihoods between software modules, aiming to identify components more625 likely to detect errors.626 6.1.0.2 Error Propagation Analysis (EPA)627 EPA techniques examine how runtime faults spread during system execution. Rubio-González and Liblit8 perform interprocedural628 static analysis to detect error propagation by comparing program execution traces to a golden (fault-free) run. For concurrent629 systems, Chan 9 addresses propagation analysis in multi-threaded applications, where golden runs are harder to establish, by630 automatically deriving invariants through source code instrumentation. Cinque et al.10 propose an empirical approach using631 runtime log analysis to build error propagation graphs, highlighting real-world traceability challenges in FEP detection.632 6.1.0.3 Sensitivity Analysis and the PIE Framework633 A major body of research examines the probability of a fault leading to an observable failure, known as sensitivity. This is634 commonly modeled using the Propagation, Infection, Execution (PIE) framework introduced by V oas11,12, which decomposes635 failure probability into three conditional probabilities for each step. Based on this framework, sensitivity analysis indicates the636 probability that a fault at a program location will lead to a failure. This work was later extended by V oas13 to address optimal637 oracle placement, where the most sensitive locations are the best candidates for placing assertions to check internal states. Xiong638 et al. 14 extend this idea by proposing inner oracles, i.e., input-specific assertions on internal states, to improve fault detection.639 However, as Jahangirova et al.2 demonstrate, FEP is rare in unit testing and becomes more prominent at the system level, where640 internal states are generally inaccessible to black-box testing techniques, making the use of inner oracles less practical.641 6.1.0.4 FEP in Coverage-Based Fault Localization (CBFL)642 In CBFL, coincidental correctness—tests that execute faulty code without causing a failure—is often treated as a symptom of643 FEP. Techniques such as those in15,16 attempt to refine fault localization using enhanced coverage metrics. Clustering-based644 and learning-based approaches 17,18,19,20 aim to identify coincidentally correct tests and isolate them from failing ones, thus645 improving the accuracy of diagnosis.646 6.1.0.5 Information-Theoretic Approaches647 FEP has also been studied through the lens of information theory. Androutsopoulos et al. 21 define squeeziness as the probability648 that different program states yield identical outputs, leading to FEP. They propose several entropy-based metrics to estimate649 this risk. Ibias et al. 22 extend this work by developing a neural network-based expert system trained on thousands of synthetic650 systems to predict FEP likelihood. Clark et al. 23 introduce normalized squeeziness, which accounts for differences in input651 domain sizes and enables more consistent comparisons across systems.652 The main difference between these works and ours is that they focus on estimating the presence of FEP in the system, whereas653 we aim to generate tests with a higher chance of revealing FEP explicitly. This difference leads to each approach providing654 estimates and hotspot locations where FEP can occur, while our approach generates test cases that make the presence of FEP655 explicit in the system outputs.656 6.2 Data-Flow in Functional Programs657 While data-flow coverage is well-studied in procedural programming, its extension to functional languages is relatively limited.658 Widera3,24 formalizes the concepts of data flow in declarative programs and provides the required definitions, focusing on a659 USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 19 subset of Erlang. They note that calculating the flow graph is not possible without considering the data flow of the application,660 due to the presence of higher-order functions, which are treated as expressions in the initial extraction of the flow graph. To661 support immutable variables, they introduce the concept of du-chains, where passing a value through multiple variables is662 considered one du-path. A key point in their work is that Erlang does not employ lazy evaluation, which leads to new challenges663 in defining the flow graph.664 Fischer et al. 25 propose a mechanism and tool for calculating data flow in Haskell programs. Their approach transforms665 Haskell programs into Curry, augmenting the program to extract the definitions and usages of variables. Converting to Curry is666 advantageous because, while it preserves Haskell’s lazy evaluation, Curry enables execution using free variables. They perform a667 symbolic execution-like approach to extract data flows, supporting dynamic narrowing to capture def-use relationships. Although668 they claim their approach can work with pre-generated input values, no implementation has been provided to test this claim.669 Moreover, their approach incurs high execution times, making it impractical for use in test case generation via meta-heuristic670 algorithms. Consequently, we developed our own mechanism for extracting data flow information from program execution.671 7 CONCLUSION672 In this paper, we addressed the persistent challenge of Failed Error Propagation (FEP) in black-box testing of stateful transactional673 services. While traditional black-box testing approaches often focus on output correctness, they typically fail to detect silent674 internal state corruptions that do not immediately manifest in observable outputs. To bridge this gap, we proposed a novel675 FEP-aware test generation technique based on functional specifications written in Haskell.676 Our approach integrates du-path coverage over internal state variables as a key fitness objective within a multi-objective genetic677 algorithm. By supporting multi-request test cases and instrumenting specifications for runtime coverage tracking, we were able678 to generate test suites that more effectively detect FEP. We validated our method on a real-world financial application—a stock679 exchange order matching engine—and uncovered subtle yet critical faults that had previously gone undetected despite extensive680 manual and automated testing.681 Empirical evaluation showed that combining expression coverage with data-flow coverage leads to significantly better682 mutation scores, confirming the hypothesis that FEP detection benefits from a balanced focus on both control and data flow.683 Furthermore, our results demonstrated that parameter configurations such as test case size, test suite size, and input value ranges684 meaningfully impact fault detection effectiveness. This highlights the importance of parameter optimization in our test case685 generation approach.686 Overall, our research demonstrates that functional specifications can serve not only as a foundation for test generation but also687 as a powerful tool for uncovering hard-to-find, state-related defects. Future work includes fully automating data flow analysis for688 lazy functional programs and extending this approach to other domains with complex internal states and long execution traces.689 REFERENCES690 1. Zakeriyan A, Khosravi R, Safari H, Khamespanah E, Shamsabadi SM. Automated testing of an industrial stock market trading platform based on691 functional specification. Science of Computer Programming. 2023;225:102908.692 2. Jahangirova G, Clark D, Harman M, Tonella P. An empirical study on failed error propagation in Java programs with real faults.arXiv preprint693 arXiv:2011.10787. 2020.694 3. Widera M. Data flow considerations for source code directed testing of functional programs. In: 2004.695 4. Shivers O. Control flow analysis in Scheme. In: 1988:164–174.696 5. Fraser G, Arcuri A. Whole test suite generation. IEEE Transactions on Software Engineering. 2012;39(2):276–291.697 6. Malik P, Nautiyal L, Ram M. A method for considering error propagation in reliability estimation of component-based software systems.698 International Journal of Mathematical, Engineering and Management Sciences. 2019;4(3):635.699 7. Abdelmoez W, Nassar D, Shereshevsky M, et al. Error propagation in software architectures. In: IEEE. 2004:384–393.700 8. Rubio-González C, Gunawi HS, Liblit B, Arpaci-Dusseau RH, Arpaci-Dusseau AC. Error propagation analysis for file systems. In: 2009:270–280.701 9. Chan A, Winter S, Saissi H, Pattabiraman K, Suri N. Ipa: Error propagation analysis of multi-threaded programs using likely invariants. In: IEEE.702 2017:184–195.703 10. Cinque M, Della Corte R, Pecchia A. An empirical analysis of error propagation in critical software systems. Empirical Software Engineering.704 2020;25(4):2450–2484.705 11. V oas J, Morell L, Miller K. Predicting where faults can hide from testing.IEEE Software. 1991;8(2):41–48.706 12. V oas JM. PIE: A dynamic failure-based technique.IEEE Transactions on software Engineering. 1992;18(8):717.707 13. V oas JM, Miller KW. Putting assertions in their place. In: IEEE. 1994:152–157.708 14. Xiong Y , Hao D, Zhang L, Zhu T, Zhu M, Lan T. Inner oracles: Input-specific assertions on internal states. In: 2015:902–905.709 15. Wong WE, Qi Y , Zhao L, Cai KY . Effective fault localization using code coverage. In: . 1. IEEE. 2007:449–456.710 16. Baudry B, Fleurey F, Le Traon Y . Improving test suites for efficient fault localization. In: 2006:82–91.711 17. Masri W, Abou Assi R. Cleansing test suites from coincidental correctness to enhance fault-localization. In: IEEE. 2010:165–174.712 20 ZAKERIY ANET AL . 18. Masri W, Assi RA. Prevalence of coincidental correctness and mitigation of its impact on fault localization. ACM transactions on software713 engineering and methodology (TOSEM). 2014;23(1):1–28.714 19. Li Y , Liu C. Using cluster analysis to identify coincidental correctness in fault localization. In: IEEE. 2012:357–360.715 20. Xue X, Pang Y , Namin AS. Trimming test suites with coincidentally correct test cases for enhancing fault localizations. In: IEEE. 2014:239–244.716 21. Androutsopoulos K, Clark D, Dan H, Hierons RM, Harman M. An analysis of the relationship between conditional entropy and failed error717 propagation in software testing. In: 2014:573–583.718 22. Ibias A, Núñez M. SqSelect: Automatic assessment of failed error propagation in state-based systems. Expert Systems with Applications.719 2021;174:114748.720 23. Clark D, Hierons RM, Patel K. Normalised squeeziness and failed error propagation. Information Processing Letters. 2019;149:6–9.721 24. Widera M. Data flow coverage for testing Erlang programs. In: 2005.722 25. Fischer S, Kuchen H. Data-flow testing of declarative programs. ACM Sigplan Notices. 2008;43(9):201–212.723 724 APPENDIX725 A HANDLERS AND DECORATORS726 The code in Listing 5 presents a sample parts of the Haskell specification as a modular and composable request-processing727 pipeline for handling new orders, using monadic instrumentation to track coverage information(described at B).728 Listing 5 Defining Data Flow Points Using Monads in Specification 1 type Handler = Request -> MEState -> Coverage (Response, MEState) 2 type Decorator = Handler -> Handler 3 4 newOrderMatcher :: Handler 5 newOrderMatcher (NewOrderRq o) s = do 6 (ob, ts) 12 case rq of 13 (NewOrderRq o) -> do 14 { (rs, s’) handler rq s 21 22 -- other decorator functions 23 24 newOrderHandler :: Handler 25 newOrderHandler = 26 fillAndKillProc $ 27 ownershipCheck $ 28 creditLimitProc $ 29 newOrderMatcher 729 The core abstraction is a Handler, a function that takes a Request and current MEState (matching engine state), and730 returns a Response and updated state within the Coverage monad. A Decorator is a higher-order function that transforms731 a Handler into another one, enabling the composition of preprocessing or postprocessing logic around the base handler.732 The function newOrderMatcher represents the core order-matching logic: it processes a NewOrderRq request by733 invoking matchNewOrder, updating the order book, and returning the corresponding trades in the response.734 creditLimitProc is a sample Decorator that wraps a handler to enforce credit limits on incoming orders. It first735 delegates the request to the wrapped handler, and then checks whether the resulting trades comply with the broker’s credit limits.736 USING FUNCTIONAL SPECIFICATION TO DETECT FAILED ERROR PROPAGATION IN STATEFUL SERVICES 21 If the check passes, it updates the credit state and tags the execution path with the coverage label "CLP1"; otherwise, it rejects737 the order, returns the original state, and logs "CLP2".738 newOrderHandler composes multiple decorators, including creditLimitProc, fillAndKillProc, and739 ownershipCheck, around the base newOrderMatcher. This layered design promotes modularity and reuse while740 supporting fine-grained runtime tracking of control and data flow for test coverage analysis.741 B COVERAGE MONAD CODE742 The code in Listing 6 illustrates the use of Haskell’s State monad to track data flow coverage points during specification743 execution.744 Listing 6 Defining Data Flow Points Using Monads in Specification 1 type CoverageItem = String 2 type CoverageInfo = [CoverageItem] 3 4 emptyCoverage :: CoverageInfo 5 emptyCoverage = [] 6 7 coverageScore :: CoverageInfo -> Int 8 coverageScore = Set.size . Set.fromList 9 10 type Coverage = State CoverageInfo 11 12 covers :: a -> CoverageItem -> Coverage a 13 covers value item = do 14 curCoverage <- get 15 put (item:curCoverage) 16 return value 745 Here, CoverageInfo is defined as a list of strings representing unique coverage items, such as variable definitions and746 uses within the program. The function covers acts as an instrumentation helper that records a coverage point by updating the747 state with the given coverage item, while transparently passing through the associated value. This mechanism enables seamless748 embedding of coverage tracking into the functional specification, facilitating runtime monitoring of du-path coverage during test749 execution without disrupting the program’s logic.750

Text is read by the "Ask this paper" AI Q&A widget below. Extraction quality varies by source — PMC NXML preserves structure cleanly, OA-HTML may include some navigation residue, and OA-PDF can have broken hyphenation. The publisher copy (via DOI) is the canonical version.

My notes (saved in your browser only)

Ask this paper AI returns verbatim quotes from the full text · source: oa-pdf

Answers must be backed by verbatim quotes from this paper's full text. Hallucinated quotes are dropped automatically; if no verbatim passage answers the question, we say so. How this works

Citation neighborhood (no data yet)

We don't have any in-corpus citations linked to this paper yet. This is a recent paper (2025) — citers typically take a year or two to land, and the OpenAlex reference graph may still be filling in.

Source provenance

europepmc
last seen: 2026-05-20T01:45:00.602351+00:00
unpaywall
last seen: 2026-07-13T06:45:44.122212+00:00