Testing of Non-deterministic Client-server Database Applications Gwan-Hwan Hwang* Dept. Information Management National Chi-nan University Puli, Taiwan 545 Huey-Der Chu Dept. Information Management National Defense Management College Chungho, Taiwan 235 Abstract An execution of a client-server application with database access exercises a sequence of database transaction events, calls a transaction sequence (or T-sequence). A client-server database application may have non-deterministic behavior, since its multiple executions with the same input may produce different T-sequences. In this paper, we present a framework for testing all possible T-sequences of a client-server database application. Such testing is referred to as reachability testing. We define race conditions between transactions. For a collected T-sequence, we show how to change the outcomes of race conditions in order to derive race-variants, which are prefixes of other T-sequences. Finally, we discuss how to perform prefix-based testing of a database application, according to a race-variant, in order to generate new T-sequences. Keywords: client-server, client-server testing, non-deterministic behavior, SQL transaction, database 1 Introduction Given the nature of client-server applications, a significant degree of randomness exists in terms of the way in which a component or an object may be selected. Likewise, these components or objects may be selected in several different orders. Therefore, there may be non-deterministic behaviors within the client-server applications. As a result of non-deterministic behavior, multiple executions of a client-server application with the same input scripts may execute different paths and produce different results. The complexity of the client-server makes testing more difficult and poses new challenges to the development organization [2,5]. This problem might arise when the same portion of shared data is simultaneously accessed from different clients without the consideration of mutual exclusive access. Moreover, when testing client-server applications, there is rarely ever testing performed which specifically addresses the database and the statements that access it [4]. The following shows an example which a client-server application with SQL transactions was proven with * G. H. Hwang’s work was supported in part by the ROC National Science Council under grant 89-2218-E-260-016 and ROC MOE/NSC program for promoting academic excellence of universities under grant 89-E-FA04-1-4. Kuo-Chung Tai Dept. of Computer Science North Carolina State Univ. Raleigh, NC 27695-7534, USA non-deterministic behavior [1]. It is a 3-tier banking client/server application written using Java Remote Method Invocation (RMI) and Java DataBase Connectivity (JDBC). As shown in Figure 1, the same test data “test1.in” are sent to two clients which execute in “Anick” (an Windows PC) and “Kielder” (an Unix Sun workstation) respectively. The “test1.in” represents a simplified form of a series of SQL transactions which the clients are going to proceed. According to the experiments in [1], multiple executions produced different results. The reason is that different executions across the two clients may have different interleave of SQL transactions which issue by “Anick” and “Kielder”. However, there is no solution to overcome the non-deterministic behavior in [1]. What we want to find in this paper is whether or not we can find all the possible behaviors, i.e. all the possible interleave of access to the shared data, for execution of a client-server applications with SQL transactions. The reachability testing which presents in [3] provides a way to overcome the non-deterministic behavior. If every execution of P with input X terminates, reachability testing of P with input X can accomplish exhaustive testing of P with input X, and thus can determine the correctness of P with X. However, [3] only provides reachability testing for shared-memory concurrent programs. The essence of reachability testing includes both the race analysis and the prefixed-based testing. Compared with reachability testing for shared-memory model of concurrent programs, the race analysis for SQL database access is more complicated: (1) each transaction may access multiple rows among multiple tables as opposed to the shared-memory model which each “read” or “write” operation only accesses to a single variable; (2) a transaction may insert or delete rows from tables as opposed to the shared-memory model which the variables are allocated statically. We define the appropriate data structure of the transaction event and the transaction-sequence for the sophisticated race analysis. Also, the scheme to activate prefix-based testing in the client-server SQL application is not addressed in previous work. We suggest two solutions: the first one is to use the standard synchronization mechanism in SQL server and the other is to let the SQL server control and monitor the order of transaction events. In Section 2 of this paper, concept of reachability testing is presented. The distinction of the reachability testing between shared-memory concurrent programs and client-server database applications is described in Section 3. Section 4 defines the transaction race relation and shows how to detect transaction-race of two transaction events in a transaction-sequence. In Section 5, an algorithm is presented for deriving race-variants of a transaction-sequence. Section 6 briefly describes how to implement the prefix-based testing for the transaction-sequence. Section 7 summarizes this paper. 9700(account#) Current(account type) 1112(password) 2(deposit) 500 1(balance) 3(withdraw) 2(deposit) 57 5(exit) ck test1.in on Anick 9700(account#) Current(account type) 1112(password) 2(deposit) 500 1(balance) 3(withdraw) 2(deposit) 57 5(exit) Deposited 500.00 balanced is 2441.0 The balance is:2941.0 Withdraw 350.00 balance is :2591.0 Deposited 57.00 balance is :2705.0. The Banking Application Execution result of Anick Execute result of Kielder Execution result of Kielder Deposited 500.00 balanced is 2941.0 The balance is:2941.0 Withdraw 350.00 balance is :2591.0 Deposited 57.00 balance is :2648.0. **Account No.970001 from ->Anick Deposited(e32) **Account No.970001 from ->Kielder Deposited(e32) **Account No.970001 from ->Anick Check Balance(e31) **Account No.970001 from ->Kielder Check Balance(e31) **Account No.970001 from ->Kielder Withdraw(e33) **Account No.970001 from ->Anick Withdraw(e33) **Account No.970001 from ->Kielder Deposited(e32) **Account No.970001 from ->Anick Deposited(e32) test1.in on Kielder Transaction processed by the SQL server in total order Figure 1: Two applications under test with the same input 2 Concepts of Reachability Testing An execution of a concurrent program non-deterministically exercises a sequence of synchronization events called a synchronization sequence (or SYN-sequence). The format of a SYN-sequence of a concurrent program depends on the concurrent constructs used. Assume that every execution of P with input X terminates. A general description of reachability testing of P with input X is the following: Step 1: Perform non-deterministic testing of P with input X to collect some (feasible) SYN-sequences. Step 2: For each collected SYN-sequence S, derive its race-variants by changing the outcome of race conditions in S. These race-variants are prefixes of other SYN-sequences of P with input X. Step 3: For each new race-variant derived in Step 2, perform prefix-based testing of P with input X and the race-variant to execute and collect additional SYN-sequences for P with input X. The prefix-based testing of P with race-variant R is first to replay all the synchronization events in R then records the subsequent synchronization events after replaying of R. Step 4: For each new SYN-sequence collected in Step 3, repeated Step 2, 3 and 4. In the database access environments in client-server perspective, P contains all the clients and databases in servers. X covers input for each client and the initial state for each database. 3 Approaches to Testing Client-server applications In the client-server environment, clients and servers are separate logical entities that work together over a network to accomplish a task. Multiple clients may request data-related services from a database concurrently. The database server responses to the clients' requests and provides secured access to shared data. The request is represented in a standard query language such as SQL. The concurrent queries of several clients to the same database may race the same portion of shared data. The majority of the modern SQL servers are transaction servers. With a transaction server, the client invokes remote procedures that reside on the server with a SQL database engine. There remote procedures on the server execute a group of SQL statements call a transaction. The SQL statements either all succeed or fail as a unit. Also, a transaction will lock the accessed data automatically. The special case of a transaction is a transaction with only one query statement. In [3], it showed how to perform reachability testing of concurrent programs using read and write operations. Compared with concurrent access to relational database, the data-sharing model of relational database is far more complicated than shared-memory model in [3]. First, the data manipulation language of relational database includes "INSERT", "DELETE", "UPDATE", "SELECT", “JOIN”, “STORED PROCEDURE”, “TRIGGER”, “RULE”, “CURSOR” and so on. A single SQL statement may refer to multiple tables which contain multiple rows (as opposed to shared-memory model which each read or write operation only access to a single shared-variable). Second, the shared-memory model in [3] is with fixed number of shared-variables which are declared and initialized before program execution. However, with the "INSERT" and "DELETE" operations in relational database, the amount of data in any table may vary depending on the transaction operations of clients. Some relational data provides execution log which records all the SQL statements issued in this server for administration. However, this kind of information is insufficient to do race analysis for transaction events. Therefore, the next section defines the transaction race relation and shows how to detect transaction-race of two transaction events in a transaction-sequence. 4 Transaction-sequences and Race Analysis for Client-server programs Let the client-server application P containing clients CL1, CL2, ..., CLn and relational database DB1, DB2, ... and DBm among all the database transaction servers, n>1, m>0. Note that there may be more than one database in a database transaction server. An execution of P exercises a sequence of database transactions, referred to a transaction-sequence (or T-sequence). More specifically, a transaction-sequence of P is denoted as (T1 , ... , Ti , ... , Tx), where Ti, 1 i x, is a transaction event. Consider the following two SQL statements: Statement 1: Read rows from “table 1” where the column value of “value1” is equal to “1”, SELECT * FROM table1 WHERE table1.value1=”1” Statement 2: Modify rows from “table 2“ where the column value of “value2” is equal to “2”, UPDATE table1 SET table1.value2=”3” WHERE table1.value2=”2” There is a race between statement 1 and 2 only if there is a row in table 1 where “value1”=”1” and “value2”=”2”. This example shows that the existence of races between two SQL statements may depend on the states of the database. Thus, for the race analysis, the data structure of a T-sequence should contain more information than the RW-sequence defined in [3]. We define the transaction-event as the following data structure. Ta={ Database_name, Database _transaction_order, Client_name, Client_transaction_order, [table_name1, row_primary_key1, operation1], . [table_namei, row_primary_keyi, operationi],, . [table_names, row_primary_keys, operations] } Ta is a transaction event. In the header of a transaction event, it records the name of the database and the name of the requested client which are "Database_name" and "Client_name" respectively. Each database is assigned a transaction number, which is initialized to one and increased by one after each transaction on this database. "Database_Transaction_order" records the relative order of this transaction among all transactions executed on the database by different clients. Similarly, the "Client_Transaction_order" indicates the relative order of this transaction among all transactions executed by the client. After the header, there is a serial of accessed row records. We have mentioned that a single SQL statement may access multiple rows from multiple tables. For the race analysis, we translate each SQL statement into multiple accessed row records. For each accessed row in a SQL statement, there is a 3-tuple representation to show the access details including "table_namei", "row_primary_keyi" and "operationi", 1 i s. They represent the table name, the corresponding primary key value of this row, and operation to this row respectively. We summarize the operation to have "insert", "read", "update", and "delete". The combination of these four operations can represent any SQL data manipulation statements. Since a transaction may contain multiple SQL statements, we should include accessed row records of each single SQL statement in the transaction event. In the follows, we define the transaction race relation of two transaction events. Two transaction events are said to have transaction-race if they may access the same portion of data in the same database and the different execution order of them may cause different execution results. Definition: For a transaction-sequence or T-sequence (T1, T2, ..., Ty), Tu={Database_nameu , Database_transaction_orderu , Client_nameu , Client_Transaction_orderu , [table_nameu1, row_primary_key u1, operation u1], . [table_nameui, row_primary_keyui, operationui], . [table_nameul, row_primary_keyul, operationul] }, and Tv={Database_namev, Database_transaction_orderv, Client_namev, Client_Transaction_orderv, [table_namev1, row_ primary_key v1, operation v1], . [table_namevj, row_primary_keyvj, operationvj], . [table_namevl’, row_primary_key vl’, operation vl’] }, where 1 u, v y and u≠v are said to have a transation-race if either (1) or (2) holds: (1) Client_nameu≠Client_namev, Database_nameu=Database_namev, and there exists i and j such that 1 i l, 1 j l’ , table_nameui=table_namevj, row_primary_keyui=row_primary_keyvj, at least one of the operationui and operationvj is "update", and none of operationui and operationvj is "insert" or “delete”. (2) Client_nameu≠Client_namev, Database_nameu=Database_namev, and there exists i and j such that 1 i l, 1 j l’ , table_nameui=table_namevj, and at least one of operationui and operationvj is "insert" or "delete". End of Definition In condition (1), it shows two transactions race the same portion in database server if two operations access the same row (the same table with the same primary key), and one of the row operations is "update" and the other is either "update" or "read". However, in condition (2), only with accessing to the same table and one of the operations is either "insert" or "delete", the race may exist. Using primary key to identify the race is sufficient for the "update" and "read" operations. It is because two separate rows in a table have different primary keys according to the definition of relational database. However, it is insufficient to use primary key to identify race for "insert" or "delete" operations. Consider the following example. Assume the table is the "table 1" of database "DBS" and the column "id" is the primary key of table 1. Id 1 2 3 4 Amount 100 200 500 1000 Client 1 issues the following operation: INSERT INTO “Table 1” VALUES (5, 700) It is to insert a row (id=5, amount=700) into table 1 in database DBS. Client 2 issues the following operation: SELECT * FROM “Table 1” WHERE “Table 1”.”amount”>=500 It selects rows from table 1 in DBS where amount 500. If the execution of Client 1 is prior to Client 2, then the transaction event of client 1 and 2 are {DBS, 1, Client 1, 1, and {DBS, 2, Client 2, 1, [table1, 5, insert] [table1, 3, read], [table1, 4, read], [table1, 5, read] } } respectively. By observing the primary key information in the two transaction event, it is obviously that the two clients race because they access the same row. However, If the execution of Client 2 is prior to Client 1, then the transaction event of client 1 and 2 are {DBS, 1, Client 2, 1, and {DBS, 2, Client 1, 1, [table1, 3, read], [table1, 4, read] [table1, 5, insert] } } respectively. The primary key information in the transaction events does not show the race. For the "delete" operation, it is with the similar situation. It is true that one "insert" and one "read" may not access to the same row. However, to make sure that we should not skip any specific race condition, we assume there is a transaction-race if one of the operations is either “delete” or “insert” operation. 5 Derive Race-variants from a Transaction-sequence Section 4 defines the format of a transaction-sequence and shows how to detect transaction-race of two transaction events in a transaction-sequence. This section presents an algorithm for deriving race-variants of a transaction-sequence. Let the client-server application P containing clients CL1, CL2, ..., CLn and relational database DB1, DB2, ... and DBm among all the database transaction servers, n > 1, m > 0. Let TS be a feasible T-sequence of P with input X. X contains input for each client and the initial state for each database. For the convenience, we definition the following notations: (1) TS(CLi) is the ordered set of the transaction events of Client CLi; (2) TS(CLi, j) is the jth transaction event of Client CLi. It is trivial that both TS(CLi) and TS(CLi, j) can be easily derived from the TS according to the definition of transaction event. 5.1 Derive the happened-before relationship between each pair of transaction events Before we present how to derive race-variants for a T-sequence, we show how to construct a directed partially-ordered graph of a T-sequence. The partially-ordered graph is used to determine if there exists a change of race in Algorithm 2 of Section 5.2. The vertices in a partially-ordered graph are transaction events. If there is a path from one vertex to another vertex in the graph, there is a happened-before relation of the two transaction events. Algorithm 1:Generat Partially-ordered graph of a T-sequence Input: TS={T1,T2,…,Tn}, a T-sequence Output: G, the produced partially-ordered graph according to TS (1) Add each transaction-event of TS to G as a vertex. (2) For the transaction events of the same client. Add edge from the transaction event with (client_transaction_order = i) to the transaction with (client_transaction_order = i+1), where 1 i < the maximum value of client_transaction_order of the client. (3) Add edge (Tx, Ty) to G if Tx and Ty have race and the database transaction number of Ty is greater than Tx. (4) Remove edge(Tp, Tq) from G if there exists another path from Tp to Tq in G, and Tp and Tq are not transaction events of the same client. End of Algorithm 1 Assume Ta and Tb are transaction evnets in G, i.e., vertices in G. Ta is happened before Tb if there is a path from Ta to Tb in G. We use the following example to illustrate Algorithm 1. Assume there are two database DB1 and DB2; three clients C0, C1 and C2; TS includes the following transaction events: TS(C1,1)={ DB1 ,1, C1,1,…,}, TS(C2,1)={ DB1 ,2, C2,1,…,}, TS(C1,2)={ DB2 ,1, C1,2,…,}, TS(C3,1)={ DB2 ,2, C3,1,…,}, TS(C2,2)={ DB2 ,3, C2,2,…,}, TS(C2,3)={ DB1 ,3, C2,3,…,}, TS(C1,3)={ DB1 ,4, C1,3,…,}, For simplicity, we only list the database name, database transaction order, client name and client transaction order in the transaction events. Also, assume the race situation is shown as following table: TS(C1,1) TS(C1,2) TS(C1,3 TS(C1,1) TS(C1,2) TS(C1,3 TS(C2,1) TS(C2,2) TS(C2,3) TS(C3,1) TS(C2,1) TS(C2,2) TS(C2,3) TS(C3,1) yes yes yes yes yes yes yes yes yes yes yes yes Notes that “yes” means there exists a transaction-race between two transaction events. After Step (1) and (2), we have G as following: TS(C1,1) TS(C2,1) TS(C1,2) TS(C2,2) TS(C1,3) TS(C2,3) TS(C3,1) After Step (3), we obtain: TS(C1,1) TS(C2,1) TS(C1,2) TS(C2,2) TS(C1,3) TS(C2,3) TS(C3,1) Finally, we derive G is as following: TS(C1,1) TS(C2,1) TS(C1,2) TS(C2,2) TS(C1,3) TS(C2,3) TS(C3,1) 5.2 Derive race-variants from transaction-sequence and its partially-ordered graph To derive the possible race-variants of TS, we construct the race-variant diagram (or RV-diagram) for TS, which is a tree with each node representing a prefix or race-variant of TS. The nodes in the RV diagram for TS are generated by considering all the possible interleavings of transaction events. For a node in the RV diagram for TS, the path from the root node of the diagram to this node is a totally-ordered sequence of transaction events. The RV-diagram scheme was first proposed in [3], which uses the version number of variable access to determine if there exists a different race outcomes in the nodes of RV-diagram. However, the transaction order is insufficient for race analysis in transaction-sequence (As shown in the example of Section 5.1, TS(C1,2) and TS(C3,1) access to the same database but do not race. Reordering of this two transaction events does not change the race condition. Since version number is insufficient to determine whether race conditions are changed, the RV diagram defined in [3] is insufficient. Therefore, we add a transaction event set in the node of RV-diagram. Each node N in the RV diagram for S contains two vectors which are used in [3] and a set of transaction events which is used to determine if the race conditions are changed. See the follows: Client transaction order vector: (I1, I2, …, In), where Ij, 1 j n, is the order number of the last transaction event in client CLj that is executed for generated node N. Database transaction order vector: (E1,E2, …, Em), where Ek, 1 k m, is the transaction order number of database DBk when node N is generated. Transaction event set: contains the transaction events in order which node N is generated. It contains a race-variant if N is a race-variant node. The following algorithm shows how to drive race-variants from a T-sequence. Assume the target T-sequence is TS. Algorithm 2: Derive all the race-variants from a T-sequence Input: A T-sequence TS Output: A set RV which contains all the possible race-variants of TS (1) Call Algorithm 1 to generate partially-ordered graph G of TS (2) Initially, the RV diagram of TS contains the root node only, set its client transaction order vector to (0,0,…,0), its database transaction order vector to (0,0,…,0), and transaction event set to be empty set. Also, Label the root node “unmarked”. (3) Select an unmarked node, say N, in the RV diagram for TS. Assume that the client transaction order vector of N is (I1, I2,…, Ij, …,In). For each j, 1<=j<=n, if Ij<the length of T(CLj), construct a child node N’ of N according to step (a) – (c). Then label N “marked” (a) Set the client transaction order vector of N’ to that of N except that the jth number is Ij+1. (b) Set the database transaction order vector of N’ to that of N. (c) Assume T(CLj,Ij+1)= {DBk , x, CLj, Ij+1, …….} and E is the kth element in database transaction order vector. Set the kth element in database transaction order vector to E+1. Add a transaction event {DBk, E+1,CLj,Ij+1,…….} to the transaction event set of N’. IF there exists a transaction event in G which is happened before T(CLj,Ij+1) and is not in the transaction event set of N’ THEN Label N’ “marked” and “race-variant node” Add the transaction event set to RV(TS) ELSE If the RV diagram for TS already contains a node with the same client transaction order vector and database transaction order vector, then label N’ “marked”, else label N’ “unmarked” (4) Repeat (3) until all the nodes are marked. End of Algorithm 2 We use the T-sequence TS in the running example of Section 5.1 to illustrate Algorithm 2. Figure 2 is part of the race-variant diagram generated from TS. Because of space limitation, we do not show the whole graph. There are two race-variant nodes. 6 Prefix-based testing of T-sequence The prefix-based testing is necessary for the reachability test. In the first phase of prefix-based testing for a race-variant R, the execution of the concurrent application is controlled to follow the order of the synchronization events in R. It is referred to as the reply phase. Note that R is only a prefix of some T-sequence. After all the synchronization events in the R are replayed, it then proceeds to the monitor phase. The concurrent application is executed and the successive synchronization events of the concurrent application are recorded. Combining R and recorded synchronization events, a feasible T-sequence of the concurrent application is obtained. The prefix-based testing for shared-memory model is presented in [6,7]. For each shared object (variable), there is a version number maintained in the system. Before the prefix-based testing, a program is processed by a preprocessor. The preprocessor inserts entry and exit section codes before and behind each synchronization statement respectively. The codes in entry and exit sections contain synchronization statements such as semaphore to fulfill the reply and monitor phases. However, the shared-memory model in [6,7] is different from the client-server SQL transaction model. We redesign the entry and exit section by using the data lock and unlock operations in the standard SQL to control the replay and monitor phase of prefix-based testing. Similar to the work in [6,7], entry and exit sections should be inserted into the source codes of the client program before the prefix-based testing. In this scenario, the SQL server does not need any special design to support reachability testing. Usually, it is impossible to get the source codes of the target concurrent applications as we may only have the binary codes. However, if a SQL server could have specific design to support reachability testing, then the source code modification is avoidable. The way to implement this is to enhance the SQL server to have the ability to understand and analyze the data structure of T-sequence, i.e., it could analyze a T-sequence to know the partial order of the transaction events of it. Before the prefix-based testing of a race-variant R proceeds, the SQL server reads and analyzes R. Then, the SQL server will control the proceeding of the transactions according to the analysis of R in the reply phase. For the second monitor phase, the SQL server just records the transactions issued by each client. Combining the recorded transaction events and R, it derives a feasible T-sequence. Since all the reply and monitor tasks are handled by the SQL server, it is no need for a preprocessor to insert the codes entry and exit sections to the source codes of the client program. 7 Conclusion Concurrent software is very difficult to test and validate because of its non-deterministic behavior which multiple executions of a single concurrent program with the same input may produce different results. In this paper, we focus on the testing of client-server applications which SQL transactions are issued by clients concurrently. We show an example to demonstrate the non-deterministic behavior. It is an application with multiple clients which issue SQL transactions to database server concurrently. In this paper, we first propose to use reachability testing to overcome the non-deterministic behavior for client-server SQL applications. Compared with reachability testing for shared-memory model of concurrent programs, the race analysis for database access is more complicated: (1) each transaction may access Race variant node Prefix node multiple rows among multiple tables as opposed to the shared-memory model which each “read” or “write” operation only accesses to a single variable; (2) a transaction may insert or delete rows from tables as opposed to the shared-memory model which the variables are allocated statically. We define the appropriate data structure of the transaction event and the transaction-sequence for the sophisticated race analysis. Accordingly, the definition of race between transaction events and an algorithm to derive the race-variants from a transaction-sequence are also presented to support reachability testing. “marked” (0,0,0) (0,0) {} Client transaction version vector Database transaction version vector Transaction event set “marked” “marked” (1,0,0) (1,0) {TS(C1,1)=[DB1,1,Cl,1]} (0,1,0) (1,0) {TS(C2,1)=[DB1,1,C2,1]} “unmarked” “unmarked” (0,0,1) (0,1) {TS(C3,1)=[DB2,1,C3,1]} “marked” (2,0,0) (1,1) {TS(C1,1)=[DB1,1,Cl,1], TS(C1,2)=[DB2,1,Cl,2] } (1,1,0) (2,0) {TS(C1,1)=[DB1,1,Cl,1], TS(C2,1)=[DB1,2,C2,1] } “unmarked” (1,0,1) (1,1) {TS(C1,1)=[DB1,1,Cl,1], TS(C3,1)=[DB2,1,C3,1]} “marked” “unmarked” (2,1,0) (2,1) {TS(C1,1)=[DB1,1,Cl,1], TS(C2,1)=[DB1,2,C2,1], TS(C1,2)=[DB2,1,C1,2]} “unmarked” (1,2,0) (2,1) {TS(C1,1)=[DB1,1,Cl,1], TS(C2,1)=[DB1,2,C2,1], TS(C2,2)=[DB2,1,C2,2]} (1,1,1) (2,1) {TS(C1,1)=[DB1,1,Cl,1], TS(C2,1)=[DB1,2,C2,1], TS(C3,1)=[DB2,1,C3,1]} Figure 2: Example of race-variant diagram Put on another way, the scheme to activate prefix-based testing in client-server SQL application is not addressed in previous work. We suggest two solutions. The first one is to use the standard synchronization mechanism in SQL server. It is similar to the previous proposed schemes. One of its drawbacks is that it needs a preprocessor to modify the source codes of the client programs. Since all the transactions issued by clients are processed by the SQL server, we propose another way for prefix-based testing. It is to let the SQL server replay and monitor the order of transaction events. Reference [1]Chu, H. and Dobson, J. Towards Quality Programming in the Automated Testing of Client/Server Applications. In Proc. of PNSQC98 and join with ICSQ'98, Oct. 1998, Oregon, USA. [2]Gerrard, P., Testing Client/Server Systems, Available at http://www.evolutif.co.uk/articles/cstesting.html. [3]Hwang, G. H., Tai, K. C., and Huang, T.L., Reachability Testing: an approach to testing concurrent software, Inter. Journal of Software Eng. and Knowledge Eng., 5, 4, (Dec. 1995), 493-510. [4]Ranade, J., Testing Client/Server Systems, McGraw-Hill, Inc., 1997. [5]Ulrich, A.W., Zimmerer, P. and Chrobok-Diening, G., Test Architectures for Testing Distributed Systems. Workshop on Testing Distributed Component-Based Systems, Los Angeles, California, May 1999. [6]Thomas J. LeBlanc and John M. Mellor-Crummey, Debugging Parallel Programs with Instant Replay, IEEE Transactions on Computers, C-36(4), pp. 471-482, April 1987. [7]Gwan-Hwan Hwang, A Systematic Parallel Testing Method for Concurrent Programs, Master Thesis, Institute of Computer Science and Information Engineering, National Chiao-Tung University, Taiwan, 1993.