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