@Test
  public void testFlatMapMaxConcurrent() {
    final int m = 4;
    final AtomicInteger subscriptionCount = new AtomicInteger();
    Observable<Integer> source =
        Observable.range(1, 10)
            .flatMap(
                new Func1<Integer, Observable<Integer>>() {
                  @Override
                  public Observable<Integer> call(Integer t1) {
                    return compose(Observable.range(t1 * 10, 2), subscriptionCount, m)
                        .subscribeOn(Schedulers.computation());
                  }
                },
                m);

    TestSubscriber<Integer> ts = new TestSubscriber<Integer>();

    source.subscribe(ts);

    ts.awaitTerminalEvent();
    ts.assertNoErrors();
    Set<Integer> expected =
        new HashSet<Integer>(
            Arrays.asList(
                10, 11, 20, 21, 30, 31, 40, 41, 50, 51, 60, 61, 70, 71, 80, 81, 90, 91, 100, 101));
    Assert.assertEquals(expected.size(), ts.getOnNextEvents().size());
    Assert.assertTrue(expected.containsAll(ts.getOnNextEvents()));
  }
  @Test
  public void testZipAsync() {
    int NUM = (int) (RxRingBuffer.SIZE * 2.1);
    AtomicInteger c1 = new AtomicInteger();
    AtomicInteger c2 = new AtomicInteger();
    TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
    Observable<Integer> zipped =
        Observable.zip(
            incrementingIntegers(c1).subscribeOn(Schedulers.computation()),
            incrementingIntegers(c2).subscribeOn(Schedulers.computation()),
            new Func2<Integer, Integer, Integer>() {

              @Override
              public Integer call(Integer t1, Integer t2) {
                return t1 + t2;
              }
            });

    zipped.take(NUM).subscribe(ts);
    ts.awaitTerminalEvent();
    ts.assertNoErrors();
    System.out.println(
        "testZipAsync => Received: "
            + ts.getOnNextEvents().size()
            + "  Emitted: "
            + c1.get()
            + " / "
            + c2.get());
    assertEquals(NUM, ts.getOnNextEvents().size());
    assertTrue(c1.get() < RxRingBuffer.SIZE * 3);
    assertTrue(c2.get() < RxRingBuffer.SIZE * 3);
  }
  @Test
  public void testFlatMapSync() {
    int NUM = (int) (RxRingBuffer.SIZE * 2.1);
    AtomicInteger c = new AtomicInteger();
    TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
    incrementingIntegers(c)
        .flatMap(
            new Func1<Integer, Observable<Integer>>() {

              @Override
              public Observable<Integer> call(Integer i) {
                return incrementingIntegers(new AtomicInteger()).take(10);
              }
            })
        .take(NUM)
        .subscribe(ts);
    ts.awaitTerminalEvent();
    ts.assertNoErrors();
    System.out.println(
        "testFlatMapSync => Received: " + ts.getOnNextEvents().size() + "  Emitted: " + c.get());
    assertEquals(NUM, ts.getOnNextEvents().size());
    // expect less than 1 buffer since the flatMap is emitting 10 each time, so it is NUM/10 that
    // will be taken.
    assertTrue(c.get() < RxRingBuffer.SIZE);
  }
  @Test
  public void testMergeAsyncThenObserveOnLoop() {
    for (int i = 0; i < 500; i++) {
      if (i % 10 == 0) {
        System.out.println("testMergeAsyncThenObserveOnLoop >> " + i);
      }
      // Verify there is no MissingBackpressureException
      int NUM = (int) (RxRingBuffer.SIZE * 4.1);
      AtomicInteger c1 = new AtomicInteger();
      AtomicInteger c2 = new AtomicInteger();

      TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
      Observable<Integer> merged =
          Observable.merge(
              incrementingIntegers(c1).subscribeOn(Schedulers.computation()),
              incrementingIntegers(c2).subscribeOn(Schedulers.computation()));

      merged.observeOn(Schedulers.io()).take(NUM).subscribe(ts);
      ts.awaitTerminalEvent();
      ts.assertNoErrors();
      System.out.println(
          "testMergeAsyncThenObserveOn => Received: "
              + ts.getOnNextEvents().size()
              + "  Emitted: "
              + c1.get()
              + " / "
              + c2.get());
      assertEquals(NUM, ts.getOnNextEvents().size());
    }
  }
  @Test
  public void testMergeAsyncThenObserveOn() {
    int NUM = (int) (RxRingBuffer.SIZE * 4.1);
    AtomicInteger c1 = new AtomicInteger();
    AtomicInteger c2 = new AtomicInteger();
    TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
    Observable<Integer> merged =
        Observable.merge(
            incrementingIntegers(c1).subscribeOn(Schedulers.computation()),
            incrementingIntegers(c2).subscribeOn(Schedulers.computation()));

    merged.observeOn(Schedulers.newThread()).take(NUM).subscribe(ts);
    ts.awaitTerminalEvent();
    ts.assertNoErrors();
    System.out.println(
        "testMergeAsyncThenObserveOn => Received: "
            + ts.getOnNextEvents().size()
            + "  Emitted: "
            + c1.get()
            + " / "
            + c2.get());
    assertEquals(NUM, ts.getOnNextEvents().size());
    // either one can starve the other, but neither should be capable of doing more than 5 batches
    // (taking 4.1)
    // TODO is it possible to make this deterministic rather than one possibly starving the other?
    // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair"
    // algoritms generally take a performance hit
    // akarnokd => run this in a loop over 10k times and never saw values get as high as 7*SIZE, but
    // since observeOn delays the unsubscription non-deterministically, the test will remain
    // unreliable
    assertTrue(c1.get() < RxRingBuffer.SIZE * 7);
    assertTrue(c2.get() < RxRingBuffer.SIZE * 7);
  }
  @Test(timeout = 2000)
  public void testOnBackpressureBuffer() {
    int NUM = (int) (RxRingBuffer.SIZE * 1.1); // > 1 so that take doesn't prevent buffer overflow
    AtomicInteger c = new AtomicInteger();
    TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
    firehose(c)
        .takeWhile(
            new Func1<Integer, Boolean>() {

              @Override
              public Boolean call(Integer t1) {
                return t1 < 100000;
              }
            })
        .onBackpressureBuffer()
        .observeOn(Schedulers.computation())
        .map(SLOW_PASS_THRU)
        .take(NUM)
        .subscribe(ts);
    ts.awaitTerminalEvent();
    ts.assertNoErrors();
    System.out.println(
        "testOnBackpressureBuffer => Received: "
            + ts.getOnNextEvents().size()
            + "  Emitted: "
            + c.get());
    assertEquals(NUM, ts.getOnNextEvents().size());
    // it buffers, so we should get the right value sequentially
    assertEquals(NUM - 1, ts.getOnNextEvents().get(NUM - 1).intValue());
  }
  @Test
  public void testMergeSync() {
    int NUM = (int) (RxRingBuffer.SIZE * 4.1);
    AtomicInteger c1 = new AtomicInteger();
    AtomicInteger c2 = new AtomicInteger();
    TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
    Observable<Integer> merged =
        Observable.merge(incrementingIntegers(c1), incrementingIntegers(c2));

    merged.take(NUM).subscribe(ts);
    ts.awaitTerminalEvent();
    ts.assertNoErrors();
    System.out.println("Expected: " + NUM + " got: " + ts.getOnNextEvents().size());
    System.out.println(
        "testMergeSync => Received: "
            + ts.getOnNextEvents().size()
            + "  Emitted: "
            + c1.get()
            + " / "
            + c2.get());
    assertEquals(NUM, ts.getOnNextEvents().size());
    // either one can starve the other, but neither should be capable of doing more than 5 batches
    // (taking 4.1)
    // TODO is it possible to make this deterministic rather than one possibly starving the other?
    // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair"
    // algoritms generally take a performance hit
    assertTrue(c1.get() < RxRingBuffer.SIZE * 5);
    assertTrue(c2.get() < RxRingBuffer.SIZE * 5);
  }
  @Test
  public void testObserveOnWithSlowConsumer() {
    int NUM = (int) (RxRingBuffer.SIZE * 0.2);
    AtomicInteger c = new AtomicInteger();
    TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
    incrementingIntegers(c)
        .observeOn(Schedulers.computation())
        .map(
            new Func1<Integer, Integer>() {

              @Override
              public Integer call(Integer i) {
                try {
                  Thread.sleep(1);
                } catch (InterruptedException e) {
                  e.printStackTrace();
                }
                return i;
              }
            })
        .take(NUM)
        .subscribe(ts);
    ts.awaitTerminalEvent();
    ts.assertNoErrors();
    System.out.println(
        "testObserveOnWithSlowConsumer => Received: "
            + ts.getOnNextEvents().size()
            + "  Emitted: "
            + c.get());
    assertEquals(NUM, ts.getOnNextEvents().size());
    assertTrue(c.get() < RxRingBuffer.SIZE * 2);
  }
 @Test
 public void testObserveOn() {
   int NUM = (int) (RxRingBuffer.SIZE * 2.1);
   AtomicInteger c = new AtomicInteger();
   TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
   incrementingIntegers(c).observeOn(Schedulers.computation()).take(NUM).subscribe(ts);
   ts.awaitTerminalEvent();
   ts.assertNoErrors();
   System.out.println(
       "testObserveOn => Received: " + ts.getOnNextEvents().size() + "  Emitted: " + c.get());
   assertEquals(NUM, ts.getOnNextEvents().size());
   assertTrue(c.get() < RxRingBuffer.SIZE * 4);
 }
  @Test(timeout = 10000)
  public void testOnBackpressureDropSynchronous() {
    for (int i = 0; i < 100; i++) {
      int NUM = (int) (RxRingBuffer.SIZE * 1.1); // > 1 so that take doesn't prevent buffer overflow
      AtomicInteger c = new AtomicInteger();
      TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
      firehose(c).onBackpressureDrop().map(SLOW_PASS_THRU).take(NUM).subscribe(ts);
      ts.awaitTerminalEvent();
      ts.assertNoErrors();

      List<Integer> onNextEvents = ts.getOnNextEvents();
      assertEquals(NUM, onNextEvents.size());

      Integer lastEvent = onNextEvents.get(NUM - 1);

      System.out.println(
          "testOnBackpressureDrop => Received: "
              + onNextEvents.size()
              + "  Emitted: "
              + c.get()
              + " Last value: "
              + lastEvent);
      // it drop, so we should get some number far higher than what would have sequentially
      // incremented
      assertTrue(NUM - 1 <= lastEvent.intValue());
    }
  }
  @Test(groups = "standalone")
  public void testObserveMultiple() {
    final TestSubscriber<Response> tester = new TestSubscriber<>();

    try (AsyncHttpClient client = asyncHttpClient()) {
      Observable<Response> o1 =
          AsyncHttpObservable.observe(() -> client.prepareGet("http://gatling.io"));
      Observable<Response> o2 =
          AsyncHttpObservable.observe(
              () -> client.prepareGet("http://www.wisc.edu").setFollowRedirect(true));
      Observable<Response> o3 =
          AsyncHttpObservable.observe(
              () -> client.prepareGet("http://www.umn.edu").setFollowRedirect(true));
      Observable<Response> all = Observable.merge(o1, o2, o3);
      all.subscribe(tester);
      tester.awaitTerminalEvent();
      tester.assertTerminalEvent();
      tester.assertCompleted();
      tester.assertNoErrors();
      List<Response> responses = tester.getOnNextEvents();
      assertNotNull(responses);
      assertEquals(responses.size(), 3);
      for (Response response : responses) {
        assertEquals(response.getStatusCode(), 200);
      }
    } catch (Exception e) {
      Thread.currentThread().interrupt();
    }
  }
  @Test
  public void exampleEquivalence() {
    TestScheduler scheduler = Schedulers.test();
    TestSubscriber<Object> testerJoin = new TestSubscriber<>();
    TestSubscriber<Object> testerGroupJoin = new TestSubscriber<>();

    Observable<Long> left = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler);
    Observable<Long> right = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler);

    left.join(
            right,
            i -> Observable.timer(150, TimeUnit.MILLISECONDS, scheduler),
            i -> Observable.timer(0, TimeUnit.MILLISECONDS, scheduler),
            (l, r) -> Tuple.create(l, r))
        .take(10)
        .subscribe(testerJoin);

    left.groupJoin(
            right,
            i -> Observable.timer(150, TimeUnit.MILLISECONDS, scheduler),
            i -> Observable.timer(0, TimeUnit.MILLISECONDS, scheduler),
            (l, rs) -> rs.map(r -> Tuple.create(l, r)))
        .flatMap(i -> i)
        .take(10)
        .subscribe(testerGroupJoin);

    scheduler.advanceTimeTo(600, TimeUnit.MILLISECONDS);
    testerJoin.assertReceivedOnNext(testerGroupJoin.getOnNextEvents());
  }
  @Test
  public void testOnErrorCutsAheadOfOnNext() {
    final PublishSubject<Long> subject = PublishSubject.create();

    final AtomicLong counter = new AtomicLong();
    TestSubscriber<Long> ts =
        new TestSubscriber<Long>(
            new Observer<Long>() {

              @Override
              public void onCompleted() {}

              @Override
              public void onError(Throwable e) {}

              @Override
              public void onNext(Long t) {
                // simulate slow consumer to force backpressure failure
                try {
                  Thread.sleep(1);
                } catch (InterruptedException e) {
                }
              }
            });
    subject.observeOn(Schedulers.computation()).subscribe(ts);

    // this will blow up with backpressure
    while (counter.get() < 102400) {
      subject.onNext(counter.get());
      counter.incrementAndGet();
    }

    ts.awaitTerminalEvent();
    assertEquals(1, ts.getOnErrorEvents().size());
    assertTrue(ts.getOnErrorEvents().get(0) instanceof MissingBackpressureException);
    // assert that the values are sequential, that cutting in didn't allow skipping some but
    // emitting others.
    // example [0, 1, 2] not [0, 1, 4]
    assertTrue(
        ts.getOnNextEvents().size()
            == ts.getOnNextEvents().get(ts.getOnNextEvents().size() - 1) + 1);
    // we should emit the error without emitting the full buffer size
    assertTrue(ts.getOnNextEvents().size() < RxRingBuffer.SIZE);
  }
  @Test(timeout = 30000)
  public void flatMapRangeMixedAsyncLoop() {
    for (int i = 0; i < 2000; i++) {
      if (i % 10 == 0) {
        System.out.println("flatMapRangeAsyncLoop > " + i);
      }
      TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
      Observable.range(0, 1000)
          .flatMap(
              new Func1<Integer, Observable<Integer>>() {
                final Random rnd = new Random();

                @Override
                public Observable<Integer> call(Integer t) {
                  Observable<Integer> r = Observable.just(t);
                  if (rnd.nextBoolean()) {
                    r = r.asObservable();
                  }
                  return r;
                }
              })
          .observeOn(Schedulers.computation())
          .subscribe(ts);

      ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS);
      if (ts.getOnCompletedEvents().isEmpty()) {
        System.out.println(ts.getOnNextEvents().size());
      }
      ts.assertTerminalEvent();
      ts.assertNoErrors();
      List<Integer> list = ts.getOnNextEvents();
      if (list.size() < 1000) {
        Set<Integer> set = new HashSet<Integer>(list);
        for (int j = 0; j < 1000; j++) {
          if (!set.contains(j)) {
            System.out.println(j + " missing");
          }
        }
      }
      assertEquals(1000, list.size());
    }
  }
  @Test(timeout = 10000)
  public void testOnBackpressureDropWithAction() {
    for (int i = 0; i < 100; i++) {
      final AtomicInteger emitCount = new AtomicInteger();
      final AtomicInteger dropCount = new AtomicInteger();
      final AtomicInteger passCount = new AtomicInteger();
      final int NUM = RxRingBuffer.SIZE * 3; // > 1 so that take doesn't prevent buffer overflow
      TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
      firehose(emitCount)
          .onBackpressureDrop(
              new Action1<Integer>() {
                @Override
                public void call(Integer i) {
                  dropCount.incrementAndGet();
                }
              })
          .doOnNext(
              new Action1<Integer>() {
                @Override
                public void call(Integer integer) {
                  passCount.incrementAndGet();
                }
              })
          .observeOn(Schedulers.computation())
          .map(SLOW_PASS_THRU)
          .take(NUM)
          .subscribe(ts);
      ts.awaitTerminalEvent();
      ts.assertNoErrors();

      List<Integer> onNextEvents = ts.getOnNextEvents();
      Integer lastEvent = onNextEvents.get(NUM - 1);
      System.out.println(
          testName.getMethodName()
              + " => Received: "
              + onNextEvents.size()
              + " Passed: "
              + passCount.get()
              + " Dropped: "
              + dropCount.get()
              + "  Emitted: "
              + emitCount.get()
              + " Last value: "
              + lastEvent);
      assertEquals(NUM, onNextEvents.size());
      // in reality, NUM < passCount
      assertTrue(NUM <= passCount.get());
      // it drop, so we should get some number far higher than what would have sequentially
      // incremented
      assertTrue(NUM - 1 <= lastEvent.intValue());
      assertTrue(0 < dropCount.get());
      assertEquals(emitCount.get(), passCount.get() + dropCount.get());
    }
  }
 @Test
 public void testSubscribeOnScheduling() {
   // in a loop for repeating the concurrency in this to increase chance of failure
   for (int i = 0; i < 100; i++) {
     int NUM = (int) (RxRingBuffer.SIZE * 2.1);
     AtomicInteger c = new AtomicInteger();
     ConcurrentLinkedQueue<Thread> threads = new ConcurrentLinkedQueue<Thread>();
     TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
     // observeOn is there to make it async and need backpressure
     incrementingIntegers(c, threads)
         .subscribeOn(Schedulers.computation())
         .observeOn(Schedulers.computation())
         .take(NUM)
         .subscribe(ts);
     ts.awaitTerminalEvent();
     ts.assertNoErrors();
     System.out.println(
         "testSubscribeOnScheduling => Received: "
             + ts.getOnNextEvents().size()
             + "  Emitted: "
             + c.get());
     assertEquals(NUM, ts.getOnNextEvents().size());
     assertTrue(c.get() < RxRingBuffer.SIZE * 4);
     Thread first = null;
     for (Thread t : threads) {
       System.out.println("testSubscribeOnScheduling => thread: " + t);
       if (first == null) {
         first = t;
       } else {
         if (!first.equals(t)) {
           fail("Expected to see the same thread");
         }
       }
     }
     System.out.println(
         "testSubscribeOnScheduling => Number of batch requests seen: " + threads.size());
     assertTrue(threads.size() > 1);
     System.out.println(
         "-------------------------------------------------------------------------------------------");
   }
 }
  @Test
  public void testGetArticles() throws Exception {
    final int articleNumPerPage = 30;
    TestSubscriber<Article> testSubscriber = new TestSubscriber<>();

    webApi.getArticles(1, articleNumPerPage).subscribe(testSubscriber);
    testSubscriber.assertNoErrors();
    List<Article> articleList = testSubscriber.getOnNextEvents();

    assertThat(articleList, hasSize(greaterThan(0)));
    assertThat(articleList, hasSize(lessThan(articleNumPerPage + 1)));
  }
  @Ignore // don't care for any reordering
  @Test(timeout = 10000)
  public void flatMapRangeAsyncLoop() {
    for (int i = 0; i < 2000; i++) {
      if (i % 10 == 0) {
        System.out.println("flatMapRangeAsyncLoop > " + i);
      }
      TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
      Observable.range(0, 1000)
          .flatMap(
              new Func1<Integer, Observable<Integer>>() {
                @Override
                public Observable<Integer> call(Integer t) {
                  return Observable.just(t);
                }
              })
          .observeOn(Schedulers.computation())
          .subscribe(ts);

      ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS);
      if (ts.getOnCompletedEvents().isEmpty()) {
        System.out.println(ts.getOnNextEvents().size());
      }
      ts.assertTerminalEvent();
      ts.assertNoErrors();
      List<Integer> list = ts.getOnNextEvents();
      assertEquals(1000, list.size());
      boolean f = false;
      for (int j = 0; j < list.size(); j++) {
        if (list.get(j) != j) {
          System.out.println(j + " " + list.get(j));
          f = true;
        }
      }
      if (f) {
        Assert.fail("Results are out of order!");
      }
    }
  }
  @Test
  public void testTakeFilterSkipChainAsync() {
    int NUM = (int) (RxRingBuffer.SIZE * 2.1);
    AtomicInteger c = new AtomicInteger();
    TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
    incrementingIntegers(c)
        .observeOn(Schedulers.computation())
        .skip(10000)
        .filter(
            new Func1<Integer, Boolean>() {

              @Override
              public Boolean call(Integer i) {
                return i > 11000;
              }
            })
        .take(NUM)
        .subscribe(ts);

    ts.awaitTerminalEvent();
    ts.assertNoErrors();

    // emit 10000 that are skipped
    // emit next 1000 that are filtered out
    // take NUM
    // so emitted is at least 10000+1000+NUM + extra for buffer size/threshold
    int expected = 10000 + 1000 + RxRingBuffer.SIZE * 3 + RxRingBuffer.SIZE / 2;

    System.out.println(
        "testTakeFilterSkipChain => Received: "
            + ts.getOnNextEvents().size()
            + "  Emitted: "
            + c.get()
            + " Expected: "
            + expected);
    assertEquals(NUM, ts.getOnNextEvents().size());
    assertTrue(c.get() < expected);
  }
 @Test(timeout = 2000)
 public void testFirehoseFailsAsExpected() {
   AtomicInteger c = new AtomicInteger();
   TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
   firehose(c).observeOn(Schedulers.computation()).map(SLOW_PASS_THRU).subscribe(ts);
   ts.awaitTerminalEvent();
   System.out.println(
       "testFirehoseFailsAsExpected => Received: "
           + ts.getOnNextEvents().size()
           + "  Emitted: "
           + c.get());
   assertEquals(1, ts.getOnErrorEvents().size());
   assertTrue(ts.getOnErrorEvents().get(0) instanceof MissingBackpressureException);
 }
  @Test
  @Ignore // the test is non-deterministic and can't be made deterministic
  public void testFlatMapAsync() {
    int NUM = (int) (RxRingBuffer.SIZE * 2.1);
    AtomicInteger c = new AtomicInteger();
    TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
    incrementingIntegers(c)
        .subscribeOn(Schedulers.computation())
        .flatMap(
            new Func1<Integer, Observable<Integer>>() {

              @Override
              public Observable<Integer> call(Integer i) {
                return incrementingIntegers(new AtomicInteger())
                    .take(10)
                    .subscribeOn(Schedulers.computation());
              }
            })
        .take(NUM)
        .subscribe(ts);
    ts.awaitTerminalEvent();
    ts.assertNoErrors();
    System.out.println(
        "testFlatMapAsync => Received: "
            + ts.getOnNextEvents().size()
            + "  Emitted: "
            + c.get()
            + " Size: "
            + RxRingBuffer.SIZE);
    assertEquals(NUM, ts.getOnNextEvents().size());
    // even though we only need 10, it will request at least RxRingBuffer.SIZE, and then as it
    // drains keep requesting more
    // and then it will be non-deterministic when the take() causes the unsubscribe as it is
    // scheduled on 10 different schedulers (threads)
    // normally this number is ~250 but can get up to ~1200 when RxRingBuffer.SIZE == 1024
    assertTrue(c.get() <= RxRingBuffer.SIZE * 2);
  }
Beispiel #22
0
  @Test
  public void testSimpleObject() {
    TestSubscriber<Person> subscriber = new TestSubscriber<>();
    observable.lift(new GsonConverter<Person>()).subscribe(subscriber);

    subscriber.assertNoErrors();
    subscriber.assertTerminalEvent();

    List<Person> persons = subscriber.getOnNextEvents();
    assertEquals(1, persons.size());

    Person person = persons.get(0);
    assertEquals("John", person.name);
    assertEquals(25, person.age);
    assertTrue(person.isDev);
  }
  @Test
  public void testBackpressureWithTakeAfter() {
    final AtomicInteger generated = new AtomicInteger();
    Observable<Integer> observable =
        Observable.from(
            new Iterable<Integer>() {
              @Override
              public Iterator<Integer> iterator() {
                return new Iterator<Integer>() {

                  @Override
                  public void remove() {}

                  @Override
                  public Integer next() {
                    return generated.getAndIncrement();
                  }

                  @Override
                  public boolean hasNext() {
                    return true;
                  }
                };
              }
            });

    TestSubscriber<Integer> testSubscriber =
        new TestSubscriber<Integer>() {
          @Override
          public void onNext(Integer t) {
            System.err.println("c t = " + t + " thread " + Thread.currentThread());
            super.onNext(t);
            try {
              Thread.sleep(10);
            } catch (InterruptedException e) {
            }
          }
        };

    observable.observeOn(Schedulers.newThread()).take(3).subscribe(testSubscriber);
    testSubscriber.awaitTerminalEvent();
    System.err.println(testSubscriber.getOnNextEvents());
    testSubscriber.assertReceivedOnNext(Arrays.asList(0, 1, 2));
    // it should be between the take num and requested batch size across the async boundary
    System.out.println("Generated: " + generated.get());
    assertTrue(generated.get() >= 3 && generated.get() <= RxRingBuffer.SIZE);
  }
  @Test
  public void testBackpressureWithTakeAfterAndMultipleBatches() {
    int numForBatches = RxRingBuffer.SIZE * 3 + 1; // should be 4 batches == ((3*n)+1) items
    final AtomicInteger generated = new AtomicInteger();
    Observable<Integer> observable =
        Observable.from(
            new Iterable<Integer>() {
              @Override
              public Iterator<Integer> iterator() {
                return new Iterator<Integer>() {

                  @Override
                  public void remove() {}

                  @Override
                  public Integer next() {
                    return generated.getAndIncrement();
                  }

                  @Override
                  public boolean hasNext() {
                    return true;
                  }
                };
              }
            });

    TestSubscriber<Integer> testSubscriber =
        new TestSubscriber<Integer>() {
          @Override
          public void onNext(Integer t) {
            //                System.err.println("c t = " + t + " thread " +
            // Thread.currentThread());
            super.onNext(t);
          }
        };

    observable.observeOn(Schedulers.newThread()).take(numForBatches).subscribe(testSubscriber);
    testSubscriber.awaitTerminalEvent();
    System.err.println(testSubscriber.getOnNextEvents());
    // it should be between the take num and requested batch size across the async boundary
    System.out.println("Generated: " + generated.get());
    assertTrue(
        generated.get() >= numForBatches && generated.get() <= numForBatches + RxRingBuffer.SIZE);
  }
  @Test(groups = "standalone")
  public void testObserveError() {
    final TestSubscriber<Response> tester = new TestSubscriber<>();

    try (AsyncHttpClient client = asyncHttpClient()) {
      Observable<Response> o1 =
          AsyncHttpObservable.observe(() -> client.prepareGet("http://gatling.io/ttfn"));
      o1.subscribe(tester);
      tester.awaitTerminalEvent();
      tester.assertTerminalEvent();
      tester.assertCompleted();
      tester.assertNoErrors();
      List<Response> responses = tester.getOnNextEvents();
      assertNotNull(responses);
      assertEquals(responses.size(), 1);
      assertEquals(responses.get(0).getStatusCode(), 404);
    } catch (Exception e) {
      Thread.currentThread().interrupt();
    }
  }
  @Test(timeout = 10000)
  public void testOnBackpressureDrop() {
    long t = System.currentTimeMillis();
    for (int i = 0; i < 100; i++) {
      // stop the test if we are getting close to the timeout because slow machines
      // may not get through 100 iterations
      if (System.currentTimeMillis() - t > TimeUnit.SECONDS.toMillis(9)) {
        break;
      }
      int NUM = (int) (RxRingBuffer.SIZE * 1.1); // > 1 so that take doesn't prevent buffer overflow
      AtomicInteger c = new AtomicInteger();
      TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
      firehose(c)
          .onBackpressureDrop()
          .observeOn(Schedulers.computation())
          .map(SLOW_PASS_THRU)
          .take(NUM)
          .subscribe(ts);
      ts.awaitTerminalEvent();
      ts.assertNoErrors();

      List<Integer> onNextEvents = ts.getOnNextEvents();
      assertEquals(NUM, onNextEvents.size());

      Integer lastEvent = onNextEvents.get(NUM - 1);

      System.out.println(
          "testOnBackpressureDrop => Received: "
              + onNextEvents.size()
              + "  Emitted: "
              + c.get()
              + " Last value: "
              + lastEvent);
      // it drop, so we should get some number far higher than what would have sequentially
      // incremented
      assertTrue(NUM - 1 <= lastEvent.intValue());
    }
  }