dde 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -5
- data/.gitignore +21 -22
- data/LICENSE +20 -20
- data/README.rdoc +19 -19
- data/Rakefile +60 -60
- data/VERSION +1 -1
- data/bin/dde_main +32 -32
- data/dde.gemspec +99 -99
- data/doc/ddeml.d.txt +374 -374
- data/doc/types.txt +159 -159
- data/exp/exp_client.rb +44 -44
- data/exp/exp_dde_monitor.rb +18 -18
- data/exp/exp_dde_server.rb +36 -36
- data/exp/exp_lib.rb +38 -38
- data/exp/exp_server.rb +44 -44
- data/features/dde.feature +9 -9
- data/features/support/env.rb +4 -4
- data/lib/dde.rb +9 -9
- data/lib/dde/app.rb +91 -91
- data/lib/dde/client.rb +102 -102
- data/lib/dde/dde_string.rb +32 -32
- data/lib/dde/monitor.rb +93 -93
- data/lib/dde/server.rb +40 -40
- data/lib/dde/xl_server.rb +89 -89
- data/lib/dde/xl_table.rb +190 -190
- data/spec/dde/app_shared.rb +84 -84
- data/spec/dde/app_spec.rb +7 -7
- data/spec/dde/client_spec.rb +199 -199
- data/spec/dde/dde_string_spec.rb +39 -39
- data/spec/dde/monitor_spec.rb +33 -33
- data/spec/dde/server_shared.rb +91 -91
- data/spec/dde/server_spec.rb +36 -36
- data/spec/dde/xl_server_spec.rb +63 -63
- data/spec/dde/xl_table_spec.rb +50 -50
- data/spec/spec.opts +2 -2
- data/spec/spec_helper.rb +21 -25
- metadata +35 -18
data/lib/dde/xl_table.rb
CHANGED
@@ -1,190 +1,190 @@
|
|
1
|
-
module
|
2
|
-
extend FFI::Library # todo: < Array ?
|
3
|
-
# class Conv < FFI::Union
|
4
|
-
# layout( :w, :ushort, # word should be 2 bytes, not 8
|
5
|
-
# :d, :double, # it is 8 bytes
|
6
|
-
# :b, [:char, 8]) # it is 8 bytes
|
7
|
-
# end
|
8
|
-
#
|
9
|
-
|
10
|
-
# XLTable class represents a single chunk of DDE data formatted as an Excel table
|
11
|
-
class XlTable
|
12
|
-
include Win::
|
13
|
-
|
14
|
-
# Received data types
|
15
|
-
TDT_FLOAT = 1
|
16
|
-
TDT_STRING = 2
|
17
|
-
TDT_BOOL = 3
|
18
|
-
TDT_ERROR = 4
|
19
|
-
TDT_BLANK = 5
|
20
|
-
TDT_INT = 6
|
21
|
-
TDT_SKIP = 7
|
22
|
-
TDT_TABLE = 16
|
23
|
-
|
24
|
-
TDT_TYPES = {
|
25
|
-
TDT_FLOAT => 'TDT_FLOAT',
|
26
|
-
TDT_STRING =>'TDT_STRING',
|
27
|
-
TDT_BOOL => 'TDT_BOOL',
|
28
|
-
TDT_ERROR => 'TDT_ERROR',
|
29
|
-
TDT_BLANK => 'TDT_BLANK',
|
30
|
-
TDT_INT => 'TDT_INT',
|
31
|
-
TDT_SKIP => 'TDT_SKIP',
|
32
|
-
TDT_TABLE => 'TDT_TABLE'
|
33
|
-
}
|
34
|
-
|
35
|
-
attr_accessor :topic, # topic prefix
|
36
|
-
:time, # time spent parsing last transaction data
|
37
|
-
:total_time, # total time spent parsing data
|
38
|
-
:num_trans # number of data transactions
|
39
|
-
|
40
|
-
def initialize
|
41
|
-
@table = [] # Array contains Arrays of Strings
|
42
|
-
@col = 0
|
43
|
-
@row = 0
|
44
|
-
@total_time = 0
|
45
|
-
@total_records = 0
|
46
|
-
# omitting separators for now
|
47
|
-
end
|
48
|
-
|
49
|
-
# tests if table data is empty or contains data in inconsistent state
|
50
|
-
def empty?
|
51
|
-
@table.empty? ||
|
52
|
-
@row == 0 || @col == 0 ||
|
53
|
-
@row != @table.size ||
|
54
|
-
@col != @table.first.size # assumes first element is also an Array
|
55
|
-
end
|
56
|
-
|
57
|
-
def data?;
|
58
|
-
!empty?
|
59
|
-
end
|
60
|
-
|
61
|
-
def draw
|
62
|
-
return false if empty?
|
63
|
-
Encoding.default_external = 'cp866'
|
64
|
-
# omitting separator gymnastics for now
|
65
|
-
cout "-----\n"
|
66
|
-
@table.each{|row| cout @topic; row.each {|col| cout " #{col}"}; cout "\n"}
|
67
|
-
end
|
68
|
-
|
69
|
-
def debug
|
70
|
-
return false if empty?
|
71
|
-
Encoding.default_external = 'cp866'
|
72
|
-
# omitting separator gymnastics for now
|
73
|
-
cout "-----\n"
|
74
|
-
@table.each_with_index{|row, i| (cout @topic, i; p row) unless row == []}
|
75
|
-
STDIN.gets
|
76
|
-
end
|
77
|
-
|
78
|
-
def receive(data_handle, mode = :collect)
|
79
|
-
$mode = mode
|
80
|
-
start = Time.now
|
81
|
-
|
82
|
-
@offset = 0
|
83
|
-
@pos = 0 #; @c=0; @r=0
|
84
|
-
|
85
|
-
@data, total_size = dde_get_data(data_handle) #dde_access_data(dde_handle)
|
86
|
-
p @data.get_bytes(0, total_size) if $mode == :debug
|
87
|
-
|
88
|
-
return nil unless @data && # DDE data is present at given dde_handle
|
89
|
-
read_int == TDT_TABLE && # and, first data block is tdtTable
|
90
|
-
read_int == 4 # and, its length is 4 bytes
|
91
|
-
|
92
|
-
@row = read_int
|
93
|
-
@col = read_int
|
94
|
-
return nil unless @row != 0 && @col != 0 # Make sure nonzero row and col
|
95
|
-
|
96
|
-
p "data set size #{total_size}, row #{@row}, col #{@col}" if $mode == :debug
|
97
|
-
@strings = @floats = @flints = @ints = @blanks = @skips = @bools = @errors = 0
|
98
|
-
|
99
|
-
@table = Array.new(@row){||Array.new}
|
100
|
-
|
101
|
-
while @offset <= total_size-4 # Need at least 4 bytes ahead to read data type and size
|
102
|
-
type = read_int # Next data field(s) type
|
103
|
-
size = read_int # Next data field(s) length in bytes
|
104
|
-
|
105
|
-
p "type #{TDT_TYPES[type]}, cb #{size}, row #{@pos/@col}, col #{@pos%@col}" if $mode == :debug
|
106
|
-
case type
|
107
|
-
when TDT_STRING # Strings, length byte followed by chars, no zero termination
|
108
|
-
field_end = @offset + size
|
109
|
-
while @offset < field_end do
|
110
|
-
length = read_char
|
111
|
-
self.table = @data.get_bytes(@offset, length) #read_bytes(length)#.force_encoding('CP1251').encode('CP866')
|
112
|
-
@offset += length
|
113
|
-
@strings += 1
|
114
|
-
end
|
115
|
-
when TDT_FLOAT # Float, 8 bytes (used to represent Integers too in Quik!)
|
116
|
-
(size/8).times do
|
117
|
-
float_or_int = @data.get_float64(@offset) # self.table = read_double
|
118
|
-
@offset += 8
|
119
|
-
int = float_or_int.round
|
120
|
-
self.table = float_or_int == int ? (@flints += 1; int) : (@floats +=1; float_or_int)
|
121
|
-
end
|
122
|
-
when TDT_BLANK # Number of blank cells, 2 bytes
|
123
|
-
(size/2).times { read_int.times { self.table = ""; @blanks += 1 } }
|
124
|
-
when TDT_SKIP # Number of cells to skip, 2 bytes - in Quik, it means that these cells contain 0
|
125
|
-
(size/2).times { read_int.times { self.table = 0; @skips += 1 } }
|
126
|
-
when TDT_INT # Int, 2 bytes
|
127
|
-
(size/2).times { self.table = read_int; @ints += 1 }
|
128
|
-
when TDT_BOOL # Bool, 2 bytes 0/1
|
129
|
-
(size/2).times { self.table = read_int == 0; @bools += 1 }
|
130
|
-
when TDT_ERROR # Error enum, 2 bytes
|
131
|
-
(size/2).times { self.table = "Error:#{read_int}"; @errors += 1 }
|
132
|
-
else
|
133
|
-
cout "Type: #{type}, #{TDT_TYPES[type]}"
|
134
|
-
return nil
|
135
|
-
end
|
136
|
-
end
|
137
|
-
#TODO: free FFI::Pointer ? delete []data; // Free memory
|
138
|
-
@time = Time.now - start
|
139
|
-
@total_time += @time
|
140
|
-
@total_records += @row
|
141
|
-
#dde_unaccess_data(dde_handle)
|
142
|
-
true # Data acquisition successful
|
143
|
-
end
|
144
|
-
|
145
|
-
def timer
|
146
|
-
cout "Last: #{@row} in #{@time} s(#{@time/@row} s/rec), total: #{@total_records} in #{
|
147
|
-
@total_time} s(#{@total_time/@total_records} s/rec)\n"
|
148
|
-
end
|
149
|
-
|
150
|
-
def formats
|
151
|
-
cout "Strings #{@strings} Floats #{@floats} FlInts #{@flints} Ints #{@ints} Blanks #{
|
152
|
-
@blanks} Skips #{@skips} Bools #{@bools} Errors #{@errors}\n"
|
153
|
-
end
|
154
|
-
|
155
|
-
def read_char
|
156
|
-
@offset += 1
|
157
|
-
@data.get_int8(@offset-1)
|
158
|
-
end
|
159
|
-
|
160
|
-
def read_int
|
161
|
-
@offset += 2
|
162
|
-
@data.get_int16(@offset-2)
|
163
|
-
end
|
164
|
-
|
165
|
-
def read_double
|
166
|
-
@offset += 8
|
167
|
-
@data.get_float64(@offset-8)
|
168
|
-
end
|
169
|
-
|
170
|
-
def read_bytes(length=1)
|
171
|
-
@offset += length
|
172
|
-
@data.get_bytes(@offset-length, length)
|
173
|
-
end
|
174
|
-
|
175
|
-
def table=(value)
|
176
|
-
# @table[@r][@c] = value
|
177
|
-
# todo: Add code for adding value to data row here (pack?)
|
178
|
-
# @c+=1
|
179
|
-
# if @c == @col
|
180
|
-
# @c =0
|
181
|
-
# @r+=1
|
182
|
-
# end
|
183
|
-
# todo: Add code for (sync!) publishing of assembled data row here (bunny? rosetta_queue?)
|
184
|
-
p value if $mode == :debug
|
185
|
-
@table[@pos/@col][@pos%@col] = value
|
186
|
-
@pos += 1
|
187
|
-
end
|
188
|
-
|
189
|
-
end
|
190
|
-
end
|
1
|
+
module Dde
|
2
|
+
extend FFI::Library # todo: < Array ?
|
3
|
+
# class Conv < FFI::Union
|
4
|
+
# layout( :w, :ushort, # word should be 2 bytes, not 8
|
5
|
+
# :d, :double, # it is 8 bytes
|
6
|
+
# :b, [:char, 8]) # it is 8 bytes
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
|
10
|
+
# XLTable class represents a single chunk of DDE data formatted as an Excel table
|
11
|
+
class XlTable
|
12
|
+
include Win::Dde
|
13
|
+
|
14
|
+
# Received data types
|
15
|
+
TDT_FLOAT = 1
|
16
|
+
TDT_STRING = 2
|
17
|
+
TDT_BOOL = 3
|
18
|
+
TDT_ERROR = 4
|
19
|
+
TDT_BLANK = 5
|
20
|
+
TDT_INT = 6
|
21
|
+
TDT_SKIP = 7
|
22
|
+
TDT_TABLE = 16
|
23
|
+
|
24
|
+
TDT_TYPES = {
|
25
|
+
TDT_FLOAT => 'TDT_FLOAT',
|
26
|
+
TDT_STRING =>'TDT_STRING',
|
27
|
+
TDT_BOOL => 'TDT_BOOL',
|
28
|
+
TDT_ERROR => 'TDT_ERROR',
|
29
|
+
TDT_BLANK => 'TDT_BLANK',
|
30
|
+
TDT_INT => 'TDT_INT',
|
31
|
+
TDT_SKIP => 'TDT_SKIP',
|
32
|
+
TDT_TABLE => 'TDT_TABLE'
|
33
|
+
}
|
34
|
+
|
35
|
+
attr_accessor :topic, # topic prefix
|
36
|
+
:time, # time spent parsing last transaction data
|
37
|
+
:total_time, # total time spent parsing data
|
38
|
+
:num_trans # number of data transactions
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@table = [] # Array contains Arrays of Strings
|
42
|
+
@col = 0
|
43
|
+
@row = 0
|
44
|
+
@total_time = 0
|
45
|
+
@total_records = 0
|
46
|
+
# omitting separators for now
|
47
|
+
end
|
48
|
+
|
49
|
+
# tests if table data is empty or contains data in inconsistent state
|
50
|
+
def empty?
|
51
|
+
@table.empty? ||
|
52
|
+
@row == 0 || @col == 0 ||
|
53
|
+
@row != @table.size ||
|
54
|
+
@col != @table.first.size # assumes first element is also an Array
|
55
|
+
end
|
56
|
+
|
57
|
+
def data?;
|
58
|
+
!empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def draw
|
62
|
+
return false if empty?
|
63
|
+
Encoding.default_external = 'cp866'
|
64
|
+
# omitting separator gymnastics for now
|
65
|
+
cout "-----\n"
|
66
|
+
@table.each{|row| cout @topic; row.each {|col| cout " #{col}"}; cout "\n"}
|
67
|
+
end
|
68
|
+
|
69
|
+
def debug
|
70
|
+
return false if empty?
|
71
|
+
Encoding.default_external = 'cp866'
|
72
|
+
# omitting separator gymnastics for now
|
73
|
+
cout "-----\n"
|
74
|
+
@table.each_with_index{|row, i| (cout @topic, i; p row) unless row == []}
|
75
|
+
STDIN.gets
|
76
|
+
end
|
77
|
+
|
78
|
+
def receive(data_handle, mode = :collect)
|
79
|
+
$mode = mode
|
80
|
+
start = Time.now
|
81
|
+
|
82
|
+
@offset = 0
|
83
|
+
@pos = 0 #; @c=0; @r=0
|
84
|
+
|
85
|
+
@data, total_size = dde_get_data(data_handle) #dde_access_data(dde_handle)
|
86
|
+
p @data.get_bytes(0, total_size) if $mode == :debug
|
87
|
+
|
88
|
+
return nil unless @data && # DDE data is present at given dde_handle
|
89
|
+
read_int == TDT_TABLE && # and, first data block is tdtTable
|
90
|
+
read_int == 4 # and, its length is 4 bytes
|
91
|
+
|
92
|
+
@row = read_int
|
93
|
+
@col = read_int
|
94
|
+
return nil unless @row != 0 && @col != 0 # Make sure nonzero row and col
|
95
|
+
|
96
|
+
p "data set size #{total_size}, row #{@row}, col #{@col}" if $mode == :debug
|
97
|
+
@strings = @floats = @flints = @ints = @blanks = @skips = @bools = @errors = 0
|
98
|
+
|
99
|
+
@table = Array.new(@row){||Array.new}
|
100
|
+
|
101
|
+
while @offset <= total_size-4 # Need at least 4 bytes ahead to read data type and size
|
102
|
+
type = read_int # Next data field(s) type
|
103
|
+
size = read_int # Next data field(s) length in bytes
|
104
|
+
|
105
|
+
p "type #{TDT_TYPES[type]}, cb #{size}, row #{@pos/@col}, col #{@pos%@col}" if $mode == :debug
|
106
|
+
case type
|
107
|
+
when TDT_STRING # Strings, length byte followed by chars, no zero termination
|
108
|
+
field_end = @offset + size
|
109
|
+
while @offset < field_end do
|
110
|
+
length = read_char
|
111
|
+
self.table = @data.get_bytes(@offset, length) #read_bytes(length)#.force_encoding('CP1251').encode('CP866')
|
112
|
+
@offset += length
|
113
|
+
@strings += 1
|
114
|
+
end
|
115
|
+
when TDT_FLOAT # Float, 8 bytes (used to represent Integers too in Quik!)
|
116
|
+
(size/8).times do
|
117
|
+
float_or_int = @data.get_float64(@offset) # self.table = read_double
|
118
|
+
@offset += 8
|
119
|
+
int = float_or_int.round
|
120
|
+
self.table = float_or_int == int ? (@flints += 1; int) : (@floats +=1; float_or_int)
|
121
|
+
end
|
122
|
+
when TDT_BLANK # Number of blank cells, 2 bytes
|
123
|
+
(size/2).times { read_int.times { self.table = ""; @blanks += 1 } }
|
124
|
+
when TDT_SKIP # Number of cells to skip, 2 bytes - in Quik, it means that these cells contain 0
|
125
|
+
(size/2).times { read_int.times { self.table = 0; @skips += 1 } }
|
126
|
+
when TDT_INT # Int, 2 bytes
|
127
|
+
(size/2).times { self.table = read_int; @ints += 1 }
|
128
|
+
when TDT_BOOL # Bool, 2 bytes 0/1
|
129
|
+
(size/2).times { self.table = read_int == 0; @bools += 1 }
|
130
|
+
when TDT_ERROR # Error enum, 2 bytes
|
131
|
+
(size/2).times { self.table = "Error:#{read_int}"; @errors += 1 }
|
132
|
+
else
|
133
|
+
cout "Type: #{type}, #{TDT_TYPES[type]}"
|
134
|
+
return nil
|
135
|
+
end
|
136
|
+
end
|
137
|
+
#TODO: free FFI::Pointer ? delete []data; // Free memory
|
138
|
+
@time = Time.now - start
|
139
|
+
@total_time += @time
|
140
|
+
@total_records += @row
|
141
|
+
#dde_unaccess_data(dde_handle)
|
142
|
+
true # Data acquisition successful
|
143
|
+
end
|
144
|
+
|
145
|
+
def timer
|
146
|
+
cout "Last: #{@row} in #{@time} s(#{@time/@row} s/rec), total: #{@total_records} in #{
|
147
|
+
@total_time} s(#{@total_time/@total_records} s/rec)\n"
|
148
|
+
end
|
149
|
+
|
150
|
+
def formats
|
151
|
+
cout "Strings #{@strings} Floats #{@floats} FlInts #{@flints} Ints #{@ints} Blanks #{
|
152
|
+
@blanks} Skips #{@skips} Bools #{@bools} Errors #{@errors}\n"
|
153
|
+
end
|
154
|
+
|
155
|
+
def read_char
|
156
|
+
@offset += 1
|
157
|
+
@data.get_int8(@offset-1)
|
158
|
+
end
|
159
|
+
|
160
|
+
def read_int
|
161
|
+
@offset += 2
|
162
|
+
@data.get_int16(@offset-2)
|
163
|
+
end
|
164
|
+
|
165
|
+
def read_double
|
166
|
+
@offset += 8
|
167
|
+
@data.get_float64(@offset-8)
|
168
|
+
end
|
169
|
+
|
170
|
+
def read_bytes(length=1)
|
171
|
+
@offset += length
|
172
|
+
@data.get_bytes(@offset-length, length)
|
173
|
+
end
|
174
|
+
|
175
|
+
def table=(value)
|
176
|
+
# @table[@r][@c] = value
|
177
|
+
# todo: Add code for adding value to data row here (pack?)
|
178
|
+
# @c+=1
|
179
|
+
# if @c == @col
|
180
|
+
# @c =0
|
181
|
+
# @r+=1
|
182
|
+
# end
|
183
|
+
# todo: Add code for (sync!) publishing of assembled data row here (bunny? rosetta_queue?)
|
184
|
+
p value if $mode == :debug
|
185
|
+
@table[@pos/@col][@pos%@col] = value
|
186
|
+
@pos += 1
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
data/spec/dde/app_shared.rb
CHANGED
@@ -1,85 +1,85 @@
|
|
1
|
-
module
|
2
|
-
shared_examples_for "DDE App" do
|
3
|
-
before(:each ){ @app = described_class.new }
|
4
|
-
after(:each ){ @app.stop_dde if @app.dde_active?}
|
5
|
-
|
6
|
-
it 'starts with nil id and flags if no arguments given' do
|
7
|
-
@app.id.should == nil
|
8
|
-
@app.init_flags.should == nil
|
9
|
-
@app.dde_active?.should == false
|
10
|
-
end unless described_class ==
|
11
|
-
|
12
|
-
it 'starts DDE (initializes as STANDARD DDE app) with given callback block' do
|
13
|
-
@app = described_class.new {|*args|}
|
14
|
-
@app.id.should be_an Integer
|
15
|
-
@app.id.should_not == 0
|
16
|
-
@app.init_flags.should == APPCLASS_STANDARD
|
17
|
-
@app.dde_active?.should == true
|
18
|
-
end unless described_class ==
|
19
|
-
|
20
|
-
describe '#start_dde' do
|
21
|
-
it 'starts DDE with callback and default init_flags' do
|
22
|
-
res = @app.start_dde {|*args|}
|
23
|
-
res.should be_true
|
24
|
-
@app.id.should be_an Integer
|
25
|
-
@app.id.should_not == 0
|
26
|
-
@app.init_flags.should == APPCLASS_STANDARD
|
27
|
-
@app.dde_active?.should == true
|
28
|
-
end unless described_class ==
|
29
|
-
|
30
|
-
it 'returns self if success (allows method chain)' do
|
31
|
-
@app.start_dde {|*args|}.should == @app
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'starts DDE with callback and given init_flags' do
|
35
|
-
res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args|}
|
36
|
-
res.should be_true
|
37
|
-
@app.id.should be_an Integer
|
38
|
-
@app.id.should_not == 0
|
39
|
-
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
40
|
-
@app.dde_active?.should == true
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'raises InitError if no callback was given' do
|
44
|
-
lambda{ @app.start_dde}.should raise_error
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'reinitializes with new flags and callback if it was already initialized' do
|
48
|
-
@app.start_dde {|*args| 1}
|
49
|
-
old_id = @app.id
|
50
|
-
res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args| 2}
|
51
|
-
res.should be_true
|
52
|
-
@app.id.should == old_id
|
53
|
-
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
54
|
-
@app.dde_active?.should == true
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
describe '#stop_dde' do
|
59
|
-
it 'stops DDE that was active' do
|
60
|
-
@app.start_dde {|*args| 1}
|
61
|
-
|
62
|
-
@app.stop_dde
|
63
|
-
@app.id.should == nil
|
64
|
-
@app.dde_active?.should == false
|
65
|
-
end
|
66
|
-
|
67
|
-
it 'preserves init_flags after DDE is stopped (for reinitialization)' do
|
68
|
-
@app.start_dde(APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS) {|*args| 1}
|
69
|
-
|
70
|
-
@app.stop_dde
|
71
|
-
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'returns self if success (allows method chain)' do
|
75
|
-
@app.start_dde{|*args|}.stop_dde.should == @app
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'raises InitError if dde was not active first' do
|
79
|
-
lambda{ @app.stop_dde}.should raise_error
|
80
|
-
end
|
81
|
-
end unless described_class ==
|
82
|
-
|
83
|
-
|
84
|
-
end
|
1
|
+
module DdeTest
|
2
|
+
shared_examples_for "DDE App" do
|
3
|
+
before(:each ){ @app = described_class.new }
|
4
|
+
after(:each ){ @app.stop_dde if @app.dde_active?}
|
5
|
+
|
6
|
+
it 'starts with nil id and flags if no arguments given' do
|
7
|
+
@app.id.should == nil
|
8
|
+
@app.init_flags.should == nil
|
9
|
+
@app.dde_active?.should == false
|
10
|
+
end unless described_class == Dde::Monitor
|
11
|
+
|
12
|
+
it 'starts DDE (initializes as STANDARD DDE app) with given callback block' do
|
13
|
+
@app = described_class.new {|*args|}
|
14
|
+
@app.id.should be_an Integer
|
15
|
+
@app.id.should_not == 0
|
16
|
+
@app.init_flags.should == APPCLASS_STANDARD
|
17
|
+
@app.dde_active?.should == true
|
18
|
+
end unless described_class == Dde::Monitor
|
19
|
+
|
20
|
+
describe '#start_dde' do
|
21
|
+
it 'starts DDE with callback and default init_flags' do
|
22
|
+
res = @app.start_dde {|*args|}
|
23
|
+
res.should be_true
|
24
|
+
@app.id.should be_an Integer
|
25
|
+
@app.id.should_not == 0
|
26
|
+
@app.init_flags.should == APPCLASS_STANDARD
|
27
|
+
@app.dde_active?.should == true
|
28
|
+
end unless described_class == Dde::Monitor
|
29
|
+
|
30
|
+
it 'returns self if success (allows method chain)' do
|
31
|
+
@app.start_dde {|*args|}.should == @app
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'starts DDE with callback and given init_flags' do
|
35
|
+
res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args|}
|
36
|
+
res.should be_true
|
37
|
+
@app.id.should be_an Integer
|
38
|
+
@app.id.should_not == 0
|
39
|
+
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
40
|
+
@app.dde_active?.should == true
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises InitError if no callback was given' do
|
44
|
+
lambda{ @app.start_dde}.should raise_error Dde::Errors::InitError
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'reinitializes with new flags and callback if it was already initialized' do
|
48
|
+
@app.start_dde {|*args| 1}
|
49
|
+
old_id = @app.id
|
50
|
+
res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args| 2}
|
51
|
+
res.should be_true
|
52
|
+
@app.id.should == old_id
|
53
|
+
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
54
|
+
@app.dde_active?.should == true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#stop_dde' do
|
59
|
+
it 'stops DDE that was active' do
|
60
|
+
@app.start_dde {|*args| 1}
|
61
|
+
|
62
|
+
@app.stop_dde
|
63
|
+
@app.id.should == nil
|
64
|
+
@app.dde_active?.should == false
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'preserves init_flags after DDE is stopped (for reinitialization)' do
|
68
|
+
@app.start_dde(APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS) {|*args| 1}
|
69
|
+
|
70
|
+
@app.stop_dde
|
71
|
+
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns self if success (allows method chain)' do
|
75
|
+
@app.start_dde{|*args|}.stop_dde.should == @app
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'raises InitError if dde was not active first' do
|
79
|
+
lambda{ @app.stop_dde}.should raise_error Dde::Errors::InitError
|
80
|
+
end
|
81
|
+
end unless described_class == Dde::Monitor
|
82
|
+
|
83
|
+
|
84
|
+
end
|
85
85
|
end
|