1 /*
2  *
3  * Distributed under the Boost Software License, Version 1.0.(See accompanying
4  * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)
5  *
6  * See http://www.boost.org/libs/iostreams for documentation.
7  *
8  * Defines the classes operation_sequence and operation, in the namespace
9  * boost::iostreams::test, for verifying that all elements of a sequence of
10  * operations are executed, and that they are executed in the correct order.
11  *
12  * File:        libs/iostreams/test/detail/operation_sequence.hpp
13  * Date:        Mon Dec 10 18:58:19 MST 2007
14  * Copyright:   2007-2008 CodeRage, LLC
15  * Author:      Jonathan Turkanis
16  * Contact:     turkanis at coderage dot com
17  */
18 
19 #ifndef BOOST_IOSTREAMS_TEST_OPERATION_SEQUENCE_HPP_INCLUDED
20 #define BOOST_IOSTREAMS_TEST_OPERATION_SEQUENCE_HPP_INCLUDED
21 
22 #include <boost/config.hpp>  // make sure size_t is in namespace std
23 #include <cstddef>
24 #include <climits>
25 #include <map>
26 #include <stdexcept>
27 #include <string>
28 #include <utility>  // pair
29 #include <vector>
30 #include <boost/lexical_cast.hpp>
31 #include <boost/preprocessor/iteration/local.hpp>
32 #include <boost/shared_ptr.hpp>
33 #include <boost/test/test_tools.hpp>
34 #include <boost/weak_ptr.hpp>
35 
36 #ifndef BOOST_IOSTREAMS_TEST_MAX_OPERATION_ERROR
37 # define BOOST_IOSTREAMS_TEST_MAX_OPERATION_ERROR 20
38 #endif
39 
40 #define BOOST_CHECK_OPERATION_SEQUENCE(seq) \
41     BOOST_CHECK_MESSAGE(seq.is_success(), seq.message()) \
42     /**/
43 
44 namespace boost { namespace iostreams { namespace test {
45 
46 // Simple exception class with error code built in to type
47 template<int Code>
48 struct operation_error { };
49 
50 class operation_sequence;
51 
52 // Represent an operation in a sequence of operations to be executed
53 class operation {
54 public:
55     friend class operation_sequence;
operation()56     operation() : pimpl_() { }
57     void execute();
58 private:
59     static void remove_operation(operation_sequence& seq, int id);
60 
61     struct impl {
implboost::iostreams::test::operation::impl62         impl(operation_sequence& seq, int id, int error_code = -1)
63             : seq(seq), id(id), error_code(error_code)
64             { }
~implboost::iostreams::test::operation::impl65         ~impl() { remove_operation(seq, id); }
66         impl& operator=(const impl&); // Supress VC warning 4512
67         operation_sequence&  seq;
68         int                  id;
69         int                  error_code;
70     };
71     friend struct impl;
72 
operation(operation_sequence & seq,int id,int error_code=-1)73     operation(operation_sequence& seq, int id, int error_code = -1)
74         : pimpl_(new impl(seq, id, error_code))
75         { }
76 
77     shared_ptr<impl> pimpl_;
78 };
79 
80 // Represents a sequence of operations to be executed in a particular order
81 class operation_sequence {
82 public:
83     friend class operation;
operation_sequence()84     operation_sequence() { reset(); }
85 
86     //
87     // Returns a new operation.
88     // Parameters:
89     //
90     //   id - The operation id, determining the position
91     //        of the new operation in the operation sequence
92     //   error_code - If supplied, indicates that the new
93     //        operation will throw operation_error<error_code>
94     //        when executed. Must be an integer between 0 and
95     //        BOOST_IOSTREAMS_TEST_MAX_OPERATION_ERROR,
96     //        inclusive.
97     //
98     operation new_operation(int id, int error_code = INT_MAX);
99 
is_success() const100     bool is_success() const { return success_; }
is_failure() const101     bool is_failure() const { return failed_; }
102     std::string message() const;
103     void reset();
104 private:
105     void execute(int id);
106     void remove_operation(int id);
107     operation_sequence(const operation_sequence&);
108     operation_sequence& operator=(const operation_sequence&);
109 
110     typedef weak_ptr<operation::impl>  ptr_type;
111     typedef std::map<int, ptr_type>    map_type;
112 
113     map_type          operations_;
114     std::vector<int>  log_;
115     std::size_t       total_executed_;
116     int               last_executed_;
117     bool              success_;
118     bool              failed_;
119 };
120 
121 //--------------Implementation of operation-----------------------------------//
122 
execute()123 void operation::execute()
124 {
125     pimpl_->seq.execute(pimpl_->id);
126     switch (pimpl_->error_code) {
127 
128     // Implementation with one or more cleanup operations
129     #define BOOST_PP_LOCAL_MACRO(n) \
130     case n: throw operation_error<n>(); \
131     /**/
132 
133     #define BOOST_PP_LOCAL_LIMITS (1, BOOST_IOSTREAMS_TEST_MAX_OPERATION_ERROR)
134     #include BOOST_PP_LOCAL_ITERATE()
135     #undef BOOST_PP_LOCAL_MACRO
136 
137     default:
138         break;
139     }
140 }
141 
remove_operation(operation_sequence & seq,int id)142 inline void operation::remove_operation(operation_sequence& seq, int id)
143 {
144     seq.remove_operation(id);
145 }
146 
147 //--------------Implementation of operation_sequence--------------------------//
148 
new_operation(int id,int error_code)149 inline operation operation_sequence::new_operation(int id, int error_code)
150 {
151     using namespace std;
152     if ( error_code < 0 ||
153          (error_code > BOOST_IOSTREAMS_TEST_MAX_OPERATION_ERROR &&
154              error_code != INT_MAX) )
155     {
156         throw runtime_error( string("The error code ") +
157                              lexical_cast<string>(error_code) +
158                              " is out of range" );
159     }
160     if (last_executed_ != INT_MIN)
161         throw runtime_error( "Operations in progress; call reset() "
162                              "before creating more operations" );
163     map_type::const_iterator it = operations_.find(id);
164     if (it != operations_.end())
165         throw runtime_error( string("The operation ") +
166                              lexical_cast<string>(id) +
167                              " already exists" );
168     operation op(*this, id, error_code);
169     operations_.insert(make_pair(id, ptr_type(op.pimpl_)));
170     return op;
171 }
172 
message() const173 inline std::string operation_sequence::message() const
174 {
175     using namespace std;
176     if (success_)
177         return "success";
178     std::string msg = failed_ ?
179         "operations occurred out of order: " :
180         "operation sequence is incomplete: ";
181     typedef vector<int>::size_type size_type;
182     for (size_type z = 0, n = log_.size(); z < n; ++z) {
183         msg += lexical_cast<string>(log_[z]);
184         if (z < n - 1)
185             msg += ',';
186     }
187     return msg;
188 }
189 
reset()190 inline void operation_sequence::reset()
191 {
192     log_.clear();
193     total_executed_ = 0;
194     last_executed_ = INT_MIN;
195     success_ = false;
196     failed_ = false;
197 }
198 
execute(int id)199 inline void operation_sequence::execute(int id)
200 {
201     log_.push_back(id);
202     if (!failed_ && last_executed_ < id) {
203         if (++total_executed_ == operations_.size())
204             success_ = true;
205         last_executed_ = id;
206     } else {
207         success_ = false;
208         failed_ = true;
209     }
210 }
211 
remove_operation(int id)212 inline void operation_sequence::remove_operation(int id)
213 {
214     using namespace std;
215     map_type::iterator it = operations_.find(id);
216     if (it == operations_.end())
217         throw runtime_error( string("No such operation: ") +
218                              lexical_cast<string>(id) );
219     operations_.erase(it);
220 }
221 
222 } } } // End namespace boost::iostreams::test.
223 
224 #endif // #ifndef BOOST_IOSTREAMS_TEST_OPERATION_SEQUENCE_HPP_INCLUDED
225