1 // Licensed to the Apache Software Foundation (ASF) under one
2 // or more contributor license agreements. See the NOTICE file
3 // distributed with this work for additional information
4 // regarding copyright ownership. The ASF licenses this file
5 // to you under the Apache License, Version 2.0 (the
6 // "License"); you may not use this file except in compliance
7 // with the License. You may obtain a copy of the License at
8 //
9 //   http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing,
12 // software distributed under the License is distributed on an
13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 // KIND, either express or implied. See the License for the
15 // specific language governing permissions and limitations
16 // under the License.
17 
18 #[macro_use]
19 extern crate log;
20 extern crate env_logger;
21 
22 #[macro_use]
23 extern crate clap;
24 extern crate ordered_float;
25 extern crate thrift;
26 extern crate thrift_test; // huh. I have to do this to use my lib
27 
28 use ordered_float::OrderedFloat;
29 use std::collections::{BTreeMap, BTreeSet};
30 use std::fmt::Debug;
31 
32 use thrift::protocol::{TBinaryInputProtocol, TBinaryOutputProtocol, TCompactInputProtocol,
33                        TCompactOutputProtocol, TInputProtocol, TMultiplexedOutputProtocol,
34                        TOutputProtocol};
35 use thrift::transport::{ReadHalf, TBufferedReadTransport, TBufferedWriteTransport,
36                         TFramedReadTransport, TFramedWriteTransport, TIoChannel, TReadTransport,
37                         TTcpChannel, TWriteTransport, WriteHalf};
38 use thrift_test::*;
39 
main()40 fn main() {
41     env_logger::init().expect("logger setup failed");
42 
43     debug!("initialized logger - running cross-test client");
44 
45     match run() {
46         Ok(()) => info!("cross-test client succeeded"),
47         Err(e) => {
48             info!("cross-test client failed with error {:?}", e);
49             std::process::exit(1);
50         }
51     }
52 }
53 
run() -> thrift::Result<()>54 fn run() -> thrift::Result<()> {
55     // unsupported options:
56     // --domain-socket
57     // --named-pipe
58     // --anon-pipes
59     // --ssl
60     // --threads
61     let matches = clap_app!(rust_test_client =>
62         (version: "1.0")
63         (author: "Apache Thrift Developers <dev@thrift.apache.org>")
64         (about: "Rust Thrift test client")
65         (@arg host: --host +takes_value "Host on which the Thrift test server is located")
66         (@arg port: --port +takes_value "Port on which the Thrift test server is listening")
67         (@arg transport: --transport +takes_value "Thrift transport implementation to use (\"buffered\", \"framed\")")
68         (@arg protocol: --protocol +takes_value "Thrift protocol implementation to use (\"binary\", \"compact\")")
69         (@arg testloops: -n --testloops +takes_value "Number of times to run tests")
70     )
71         .get_matches();
72 
73     let host = matches.value_of("host").unwrap_or("127.0.0.1");
74     let port = value_t!(matches, "port", u16).unwrap_or(9090);
75     let testloops = value_t!(matches, "testloops", u8).unwrap_or(1);
76     let transport = matches.value_of("transport").unwrap_or("buffered");
77     let protocol = matches.value_of("protocol").unwrap_or("binary");
78 
79 
80     let mut thrift_test_client = {
81         let (i_prot, o_prot) = build_protocols(host, port, transport, protocol, "ThriftTest")?;
82         ThriftTestSyncClient::new(i_prot, o_prot)
83     };
84 
85     let mut second_service_client = if protocol.starts_with("multi") {
86         let (i_prot, o_prot) = build_protocols(host, port, transport, protocol, "SecondService")?;
87         Some(SecondServiceSyncClient::new(i_prot, o_prot))
88     } else {
89         None
90     };
91 
92     info!(
93         "connecting to {}:{} with {}+{} stack",
94         host,
95         port,
96         protocol,
97         transport
98     );
99 
100     for _ in 0..testloops {
101         make_thrift_calls(&mut thrift_test_client, &mut second_service_client)?
102     }
103 
104     Ok(())
105 }
106 
build_protocols( host: &str, port: u16, transport: &str, protocol: &str, service_name: &str, ) -> thrift::Result<(Box<TInputProtocol>, Box<TOutputProtocol>)>107 fn build_protocols(
108     host: &str,
109     port: u16,
110     transport: &str,
111     protocol: &str,
112     service_name: &str,
113 ) -> thrift::Result<(Box<TInputProtocol>, Box<TOutputProtocol>)> {
114     let (i_chan, o_chan) = tcp_channel(host, port)?;
115 
116     let (i_tran, o_tran): (Box<TReadTransport>, Box<TWriteTransport>) = match transport {
117         "buffered" => {
118             (Box::new(TBufferedReadTransport::new(i_chan)),
119              Box::new(TBufferedWriteTransport::new(o_chan)))
120         }
121         "framed" => {
122             (Box::new(TFramedReadTransport::new(i_chan)),
123              Box::new(TFramedWriteTransport::new(o_chan)))
124         }
125         unmatched => return Err(format!("unsupported transport {}", unmatched).into()),
126     };
127 
128     let (i_prot, o_prot): (Box<TInputProtocol>, Box<TOutputProtocol>) = match protocol {
129         "binary" => {
130             (Box::new(TBinaryInputProtocol::new(i_tran, true)),
131              Box::new(TBinaryOutputProtocol::new(o_tran, true)))
132         }
133         "multi" => {
134             (Box::new(TBinaryInputProtocol::new(i_tran, true)),
135              Box::new(
136                 TMultiplexedOutputProtocol::new(
137                     service_name,
138                     TBinaryOutputProtocol::new(o_tran, true),
139                 ),
140             ))
141         }
142         "compact" => {
143             (Box::new(TCompactInputProtocol::new(i_tran)),
144              Box::new(TCompactOutputProtocol::new(o_tran)))
145         }
146         "multic" => {
147             (Box::new(TCompactInputProtocol::new(i_tran)),
148              Box::new(TMultiplexedOutputProtocol::new(service_name, TCompactOutputProtocol::new(o_tran)),))
149         }
150         unmatched => return Err(format!("unsupported protocol {}", unmatched).into()),
151     };
152 
153     Ok((i_prot, o_prot))
154 }
155 
156 // FIXME: expose "open" through the client interface so I don't have to early
157 // open
tcp_channel( host: &str, port: u16, ) -> thrift::Result<(ReadHalf<TTcpChannel>, WriteHalf<TTcpChannel>)>158 fn tcp_channel(
159     host: &str,
160     port: u16,
161 ) -> thrift::Result<(ReadHalf<TTcpChannel>, WriteHalf<TTcpChannel>)> {
162     let mut c = TTcpChannel::new();
163     c.open(&format!("{}:{}", host, port))?;
164     c.split()
165 }
166 
167 type BuildThriftTestClient = ThriftTestSyncClient<Box<TInputProtocol>, Box<TOutputProtocol>>;
168 type BuiltSecondServiceClient = SecondServiceSyncClient<Box<TInputProtocol>, Box<TOutputProtocol>>;
169 
170 #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
make_thrift_calls( thrift_test_client: &mut BuildThriftTestClient, second_service_client: &mut Option<BuiltSecondServiceClient>, ) -> Result<(), thrift::Error>171 fn make_thrift_calls(
172     thrift_test_client: &mut BuildThriftTestClient,
173     second_service_client: &mut Option<BuiltSecondServiceClient>,
174 ) -> Result<(), thrift::Error> {
175     info!("testVoid");
176     thrift_test_client.test_void()?;
177 
178     info!("testString");
179     verify_expected_result(
180         thrift_test_client.test_string("thing".to_owned()),
181         "thing".to_owned(),
182     )?;
183 
184     info!("testBool");
185     verify_expected_result(thrift_test_client.test_bool(true), true)?;
186 
187     info!("testBool");
188     verify_expected_result(thrift_test_client.test_bool(false), false)?;
189 
190     info!("testByte");
191     verify_expected_result(thrift_test_client.test_byte(42), 42)?;
192 
193     info!("testi32");
194     verify_expected_result(thrift_test_client.test_i32(1159348374), 1159348374)?;
195 
196     info!("testi64");
197     // try!(verify_expected_result(thrift_test_client.test_i64(-8651829879438294565),
198     // -8651829879438294565));
199     verify_expected_result(
200         thrift_test_client.test_i64(i64::min_value()),
201         i64::min_value(),
202     )?;
203 
204     info!("testDouble");
205     verify_expected_result(
206         thrift_test_client.test_double(OrderedFloat::from(42.42)),
207         OrderedFloat::from(42.42),
208     )?;
209 
210     info!("testTypedef");
211     {
212         let u_snd: UserId = 2348;
213         let u_cmp: UserId = 2348;
214         verify_expected_result(thrift_test_client.test_typedef(u_snd), u_cmp)?;
215     }
216 
217     info!("testEnum");
218     {
219         verify_expected_result(thrift_test_client.test_enum(Numberz::TWO), Numberz::TWO)?;
220     }
221 
222     info!("testBinary");
223     {
224         let b_snd = vec![0x77, 0x30, 0x30, 0x74, 0x21, 0x20, 0x52, 0x75, 0x73, 0x74];
225         let b_cmp = vec![0x77, 0x30, 0x30, 0x74, 0x21, 0x20, 0x52, 0x75, 0x73, 0x74];
226         verify_expected_result(thrift_test_client.test_binary(b_snd), b_cmp)?;
227     }
228 
229     info!("testStruct");
230     {
231         let x_snd = Xtruct {
232             string_thing: Some("foo".to_owned()),
233             byte_thing: Some(12),
234             i32_thing: Some(219129),
235             i64_thing: Some(12938492818),
236         };
237         let x_cmp = Xtruct {
238             string_thing: Some("foo".to_owned()),
239             byte_thing: Some(12),
240             i32_thing: Some(219129),
241             i64_thing: Some(12938492818),
242         };
243         verify_expected_result(thrift_test_client.test_struct(x_snd), x_cmp)?;
244     }
245 
246     // Xtruct again, with optional values
247     // FIXME: apparently the erlang thrift server does not like opt-in-req-out
248     // parameters that are undefined. Joy.
249     // {
250     // let x_snd = Xtruct { string_thing: Some("foo".to_owned()), byte_thing: None,
251     // i32_thing: None, i64_thing: Some(12938492818) };
252     // let x_cmp = Xtruct { string_thing: Some("foo".to_owned()), byte_thing:
253     // Some(0), i32_thing: Some(0), i64_thing: Some(12938492818) }; // the C++
254     // server is responding correctly
255     // try!(verify_expected_result(thrift_test_client.test_struct(x_snd), x_cmp));
256     // }
257     //
258 
259     info!("testNest"); // (FIXME: try Xtruct2 with optional values)
260     {
261         let x_snd = Xtruct2 {
262             byte_thing: Some(32),
263             struct_thing: Some(
264                 Xtruct {
265                     string_thing: Some("foo".to_owned()),
266                     byte_thing: Some(1),
267                     i32_thing: Some(324382098),
268                     i64_thing: Some(12938492818),
269                 },
270             ),
271             i32_thing: Some(293481098),
272         };
273         let x_cmp = Xtruct2 {
274             byte_thing: Some(32),
275             struct_thing: Some(
276                 Xtruct {
277                     string_thing: Some("foo".to_owned()),
278                     byte_thing: Some(1),
279                     i32_thing: Some(324382098),
280                     i64_thing: Some(12938492818),
281                 },
282             ),
283             i32_thing: Some(293481098),
284         };
285         verify_expected_result(thrift_test_client.test_nest(x_snd), x_cmp)?;
286     }
287 
288     // do the multiplexed calls while making the main ThriftTest calls
289     if let Some(ref mut client) = second_service_client.as_mut() {
290         info!("SecondService secondtestString");
291         {
292             verify_expected_result(
293                 client.secondtest_string("test_string".to_owned()),
294                 "testString(\"test_string\")".to_owned(),
295             )?;
296         }
297     }
298 
299     info!("testList");
300     {
301         let mut v_snd: Vec<i32> = Vec::new();
302         v_snd.push(29384);
303         v_snd.push(238);
304         v_snd.push(32498);
305 
306         let mut v_cmp: Vec<i32> = Vec::new();
307         v_cmp.push(29384);
308         v_cmp.push(238);
309         v_cmp.push(32498);
310 
311         verify_expected_result(thrift_test_client.test_list(v_snd), v_cmp)?;
312     }
313 
314     info!("testSet");
315     {
316         let mut s_snd: BTreeSet<i32> = BTreeSet::new();
317         s_snd.insert(293481);
318         s_snd.insert(23);
319         s_snd.insert(3234);
320 
321         let mut s_cmp: BTreeSet<i32> = BTreeSet::new();
322         s_cmp.insert(293481);
323         s_cmp.insert(23);
324         s_cmp.insert(3234);
325 
326         verify_expected_result(thrift_test_client.test_set(s_snd), s_cmp)?;
327     }
328 
329     info!("testMap");
330     {
331         let mut m_snd: BTreeMap<i32, i32> = BTreeMap::new();
332         m_snd.insert(2, 4);
333         m_snd.insert(4, 6);
334         m_snd.insert(8, 7);
335 
336         let mut m_cmp: BTreeMap<i32, i32> = BTreeMap::new();
337         m_cmp.insert(2, 4);
338         m_cmp.insert(4, 6);
339         m_cmp.insert(8, 7);
340 
341         verify_expected_result(thrift_test_client.test_map(m_snd), m_cmp)?;
342     }
343 
344     info!("testStringMap");
345     {
346         let mut m_snd: BTreeMap<String, String> = BTreeMap::new();
347         m_snd.insert("2".to_owned(), "4_string".to_owned());
348         m_snd.insert("4".to_owned(), "6_string".to_owned());
349         m_snd.insert("8".to_owned(), "7_string".to_owned());
350 
351         let mut m_rcv: BTreeMap<String, String> = BTreeMap::new();
352         m_rcv.insert("2".to_owned(), "4_string".to_owned());
353         m_rcv.insert("4".to_owned(), "6_string".to_owned());
354         m_rcv.insert("8".to_owned(), "7_string".to_owned());
355 
356         verify_expected_result(thrift_test_client.test_string_map(m_snd), m_rcv)?;
357     }
358 
359     // nested map
360     // expect : {-4 => {-4 => -4, -3 => -3, -2 => -2, -1 => -1, }, 4 => {1 => 1, 2
361     // => 2, 3 => 3, 4 => 4, }, }
362     info!("testMapMap");
363     {
364         let mut m_cmp_nested_0: BTreeMap<i32, i32> = BTreeMap::new();
365         for i in (-4 as i32)..0 {
366             m_cmp_nested_0.insert(i, i);
367         }
368         let mut m_cmp_nested_1: BTreeMap<i32, i32> = BTreeMap::new();
369         for i in 1..5 {
370             m_cmp_nested_1.insert(i, i);
371         }
372 
373         let mut m_cmp: BTreeMap<i32, BTreeMap<i32, i32>> = BTreeMap::new();
374         m_cmp.insert(-4, m_cmp_nested_0);
375         m_cmp.insert(4, m_cmp_nested_1);
376 
377         verify_expected_result(thrift_test_client.test_map_map(42), m_cmp)?;
378     }
379 
380     info!("testMulti");
381     {
382         let mut m_snd: BTreeMap<i16, String> = BTreeMap::new();
383         m_snd.insert(1298, "fizz".to_owned());
384         m_snd.insert(-148, "buzz".to_owned());
385 
386         let s_cmp = Xtruct {
387             string_thing: Some("Hello2".to_owned()),
388             byte_thing: Some(1),
389             i32_thing: Some(-123948),
390             i64_thing: Some(-19234123981),
391         };
392 
393         verify_expected_result(
394             thrift_test_client.test_multi(1, -123948, -19234123981, m_snd, Numberz::EIGHT, 81),
395             s_cmp,
396         )?;
397     }
398 
399     // Insanity
400     // returns:
401     // { 1 => { 2 => argument,
402     //          3 => argument,
403     //        },
404     //   2 => { 6 => <empty Insanity struct>, },
405     // }
406     {
407         let mut arg_map_usermap: BTreeMap<Numberz, i64> = BTreeMap::new();
408         arg_map_usermap.insert(Numberz::ONE, 4289);
409         arg_map_usermap.insert(Numberz::EIGHT, 19);
410 
411         let mut arg_vec_xtructs: Vec<Xtruct> = Vec::new();
412         arg_vec_xtructs.push(
413             Xtruct {
414                 string_thing: Some("foo".to_owned()),
415                 byte_thing: Some(8),
416                 i32_thing: Some(29),
417                 i64_thing: Some(92384),
418             },
419         );
420         arg_vec_xtructs.push(
421             Xtruct {
422                 string_thing: Some("bar".to_owned()),
423                 byte_thing: Some(28),
424                 i32_thing: Some(2),
425                 i64_thing: Some(-1281),
426             },
427         );
428         arg_vec_xtructs.push(
429             Xtruct {
430                 string_thing: Some("baz".to_owned()),
431                 byte_thing: Some(0),
432                 i32_thing: Some(3948539),
433                 i64_thing: Some(-12938492),
434             },
435         );
436 
437         let mut s_cmp_nested_1: BTreeMap<Numberz, Insanity> = BTreeMap::new();
438         let insanity = Insanity {
439             user_map: Some(arg_map_usermap),
440             xtructs: Some(arg_vec_xtructs),
441         };
442         s_cmp_nested_1.insert(Numberz::TWO, insanity.clone());
443         s_cmp_nested_1.insert(Numberz::THREE, insanity.clone());
444 
445         let mut s_cmp_nested_2: BTreeMap<Numberz, Insanity> = BTreeMap::new();
446         let empty_insanity = Insanity {
447             user_map: Some(BTreeMap::new()),
448             xtructs: Some(Vec::new()),
449         };
450         s_cmp_nested_2.insert(Numberz::SIX, empty_insanity);
451 
452         let mut s_cmp: BTreeMap<UserId, BTreeMap<Numberz, Insanity>> = BTreeMap::new();
453         s_cmp.insert(1 as UserId, s_cmp_nested_1);
454         s_cmp.insert(2 as UserId, s_cmp_nested_2);
455 
456         verify_expected_result(thrift_test_client.test_insanity(insanity.clone()), s_cmp)?;
457     }
458 
459     info!("testException - remote throws Xception");
460     {
461         let r = thrift_test_client.test_exception("Xception".to_owned());
462         let x = match r {
463             Err(thrift::Error::User(ref e)) => {
464                 match e.downcast_ref::<Xception>() {
465                     Some(x) => Ok(x),
466                     None => Err(thrift::Error::User("did not get expected Xception struct".into()),),
467                 }
468             }
469             _ => Err(thrift::Error::User("did not get exception".into())),
470         }?;
471 
472         let x_cmp = Xception {
473             error_code: Some(1001),
474             message: Some("Xception".to_owned()),
475         };
476 
477         verify_expected_result(Ok(x), &x_cmp)?;
478     }
479 
480     info!("testException - remote throws TApplicationException");
481     {
482         let r = thrift_test_client.test_exception("TException".to_owned());
483         match r {
484             Err(thrift::Error::Application(ref e)) => {
485                 info!("received an {:?}", e);
486                 Ok(())
487             }
488             _ => Err(thrift::Error::User("did not get exception".into())),
489         }?;
490     }
491 
492     info!("testException - remote succeeds");
493     {
494         let r = thrift_test_client.test_exception("foo".to_owned());
495         match r {
496             Ok(_) => Ok(()),
497             _ => Err(thrift::Error::User("received an exception".into())),
498         }?;
499     }
500 
501     info!("testMultiException - remote throws Xception");
502     {
503         let r =
504             thrift_test_client.test_multi_exception("Xception".to_owned(), "ignored".to_owned());
505         let x = match r {
506             Err(thrift::Error::User(ref e)) => {
507                 match e.downcast_ref::<Xception>() {
508                     Some(x) => Ok(x),
509                     None => Err(thrift::Error::User("did not get expected Xception struct".into()),),
510                 }
511             }
512             _ => Err(thrift::Error::User("did not get exception".into())),
513         }?;
514 
515         let x_cmp = Xception {
516             error_code: Some(1001),
517             message: Some("This is an Xception".to_owned()),
518         };
519 
520         verify_expected_result(Ok(x), &x_cmp)?;
521     }
522 
523     info!("testMultiException - remote throws Xception2");
524     {
525         let r =
526             thrift_test_client.test_multi_exception("Xception2".to_owned(), "ignored".to_owned());
527         let x = match r {
528             Err(thrift::Error::User(ref e)) => {
529                 match e.downcast_ref::<Xception2>() {
530                     Some(x) => Ok(x),
531                     None => Err(thrift::Error::User("did not get expected Xception struct".into()),),
532                 }
533             }
534             _ => Err(thrift::Error::User("did not get exception".into())),
535         }?;
536 
537         let x_cmp = Xception2 {
538             error_code: Some(2002),
539             struct_thing: Some(
540                 Xtruct {
541                     string_thing: Some("This is an Xception2".to_owned()),
542                     // since this is an OPT_IN_REQ_OUT field the sender sets a default
543                     byte_thing: Some(0),
544                     // since this is an OPT_IN_REQ_OUT field the sender sets a default
545                     i32_thing: Some(0),
546                     // since this is an OPT_IN_REQ_OUT field the sender sets a default
547                     i64_thing: Some(0),
548                 },
549             ),
550         };
551 
552         verify_expected_result(Ok(x), &x_cmp)?;
553     }
554 
555     info!("testMultiException - remote succeeds");
556     {
557         let r = thrift_test_client.test_multi_exception("haha".to_owned(), "RETURNED".to_owned());
558         let x = match r {
559             Err(e) => Err(thrift::Error::User(format!("received an unexpected exception {:?}", e).into(),),),
560             _ => r,
561         }?;
562 
563         let x_cmp = Xtruct {
564             string_thing: Some("RETURNED".to_owned()),
565             // since this is an OPT_IN_REQ_OUT field the sender sets a default
566             byte_thing: Some(0),
567             // since this is an OPT_IN_REQ_OUT field the sender sets a default
568             i32_thing: Some(0),
569             // since this is an OPT_IN_REQ_OUT field the sender sets a default
570             i64_thing: Some(0),
571         };
572 
573         verify_expected_result(Ok(x), x_cmp)?;
574     }
575 
576     info!("testOneWay - remote sleeps for 1 second");
577     {
578         thrift_test_client.test_oneway(1)?;
579     }
580 
581     // final test to verify that the connection is still writable after the one-way
582     // call
583     thrift_test_client.test_void()
584 }
585 
586 #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
verify_expected_result<T: Debug + PartialEq + Sized>( actual: Result<T, thrift::Error>, expected: T, ) -> Result<(), thrift::Error>587 fn verify_expected_result<T: Debug + PartialEq + Sized>(
588     actual: Result<T, thrift::Error>,
589     expected: T,
590 ) -> Result<(), thrift::Error> {
591     info!("*** EXPECTED: Ok({:?})", expected);
592     info!("*** ACTUAL  : {:?}", actual);
593     match actual {
594         Ok(v) => {
595             if v == expected {
596                 info!("*** OK ***");
597                 Ok(())
598             } else {
599                 info!("*** FAILED ***");
600                 Err(thrift::Error::User(format!("expected {:?} but got {:?}", &expected, &v).into()),)
601             }
602         }
603         Err(e) => Err(e),
604     }
605 }
606