@BeforeClass
  public static void setupClass() throws Exception {
    incrementalIndex =
        new OnheapIncrementalIndex(
            0,
            QueryGranularities.NONE,
            new AggregatorFactory[] {new CountAggregatorFactory("count")},
            true,
            true,
            true,
            5000);

    StringInputRowParser parser =
        new StringInputRowParser(
            new CSVParseSpec(
                new TimestampSpec("timestamp", "iso", null),
                new DimensionsSpec(
                    DimensionsSpec.getDefaultSchemas(ImmutableList.of("product", "tags")),
                    null,
                    null),
                "\t",
                ImmutableList.of("timestamp", "product", "tags")),
            "UTF-8");

    String[] rows =
        new String[] {
          "2011-01-12T00:00:00.000Z,product_1,t1\tt2\tt3",
          "2011-01-13T00:00:00.000Z,product_2,t3\tt4\tt5",
          "2011-01-14T00:00:00.000Z,product_3,t5\tt6\tt7",
        };

    for (String row : rows) {
      incrementalIndex.add(parser.parse(row));
    }

    persistedSegmentDir = Files.createTempDir();
    TestHelper.getTestIndexMerger().persist(incrementalIndex, persistedSegmentDir, new IndexSpec());

    queryableIndex = TestHelper.getTestIndexIO().loadIndex(persistedSegmentDir);
  }
  @Parameterized.Parameters(name = "{1}")
  public static Collection<Object[]> constructorFeeder() throws IOException {
    final IndexSpec indexSpec = new IndexSpec();

    final HeapMemoryTaskStorage ts = new HeapMemoryTaskStorage(new TaskStorageConfig(null) {});
    final IncrementalIndexSchema schema =
        new IncrementalIndexSchema.Builder()
            .withQueryGranularity(QueryGranularities.NONE)
            .withMinTimestamp(JodaUtils.MIN_INSTANT)
            .withDimensionsSpec(ROW_PARSER)
            .withMetrics(
                new AggregatorFactory[] {
                  new LongSumAggregatorFactory(METRIC_LONG_NAME, DIM_LONG_NAME),
                  new DoubleSumAggregatorFactory(METRIC_FLOAT_NAME, DIM_FLOAT_NAME)
                })
            .build();
    final OnheapIncrementalIndex index =
        new OnheapIncrementalIndex(schema, true, MAX_ROWS * MAX_SHARD_NUMBER);

    for (Integer i = 0; i < MAX_ROWS; ++i) {
      index.add(ROW_PARSER.parse(buildRow(i.longValue())));
    }

    if (!persistDir.mkdirs() && !persistDir.exists()) {
      throw new IOException(
          String.format("Could not create directory at [%s]", persistDir.getAbsolutePath()));
    }
    INDEX_MERGER.persist(index, persistDir, indexSpec);

    final TaskLockbox tl = new TaskLockbox(ts);
    final IndexerSQLMetadataStorageCoordinator mdc =
        new IndexerSQLMetadataStorageCoordinator(null, null, null) {
          private final Set<DataSegment> published = Sets.newHashSet();
          private final Set<DataSegment> nuked = Sets.newHashSet();

          @Override
          public List<DataSegment> getUsedSegmentsForInterval(String dataSource, Interval interval)
              throws IOException {
            return ImmutableList.copyOf(segmentSet);
          }

          @Override
          public List<DataSegment> getUsedSegmentsForIntervals(
              String dataSource, List<Interval> interval) throws IOException {
            return ImmutableList.copyOf(segmentSet);
          }

          @Override
          public List<DataSegment> getUnusedSegmentsForInterval(
              String dataSource, Interval interval) {
            return ImmutableList.of();
          }

          @Override
          public Set<DataSegment> announceHistoricalSegments(Set<DataSegment> segments) {
            Set<DataSegment> added = Sets.newHashSet();
            for (final DataSegment segment : segments) {
              if (published.add(segment)) {
                added.add(segment);
              }
            }

            return ImmutableSet.copyOf(added);
          }

          @Override
          public void deleteSegments(Set<DataSegment> segments) {
            nuked.addAll(segments);
          }
        };
    final LocalTaskActionClientFactory tac =
        new LocalTaskActionClientFactory(ts, new TaskActionToolbox(tl, mdc, newMockEmitter()));
    SegmentHandoffNotifierFactory notifierFactory =
        EasyMock.createNiceMock(SegmentHandoffNotifierFactory.class);
    EasyMock.replay(notifierFactory);

    final TaskToolboxFactory taskToolboxFactory =
        new TaskToolboxFactory(
            new TaskConfig(tmpDir.getAbsolutePath(), null, null, 50000, null, false, null, null),
            tac,
            newMockEmitter(),
            new DataSegmentPusher() {
              @Deprecated
              @Override
              public String getPathForHadoop(String dataSource) {
                return getPathForHadoop();
              }

              @Override
              public String getPathForHadoop() {
                throw new UnsupportedOperationException();
              }

              @Override
              public DataSegment push(File file, DataSegment segment) throws IOException {
                return segment;
              }
            },
            new DataSegmentKiller() {
              @Override
              public void kill(DataSegment segments) throws SegmentLoadingException {}
            },
            new DataSegmentMover() {
              @Override
              public DataSegment move(DataSegment dataSegment, Map<String, Object> targetLoadSpec)
                  throws SegmentLoadingException {
                return dataSegment;
              }
            },
            new DataSegmentArchiver() {
              @Override
              public DataSegment archive(DataSegment segment) throws SegmentLoadingException {
                return segment;
              }

              @Override
              public DataSegment restore(DataSegment segment) throws SegmentLoadingException {
                return segment;
              }
            },
            null, // segment announcer
            notifierFactory,
            null, // query runner factory conglomerate corporation unionized collective
            null, // query executor service
            null, // monitor scheduler
            new SegmentLoaderFactory(
                new SegmentLoaderLocalCacheManager(
                    null,
                    new SegmentLoaderConfig() {
                      @Override
                      public List<StorageLocationConfig> getLocations() {
                        return Lists.newArrayList();
                      }
                    },
                    MAPPER)),
            MAPPER,
            INDEX_MERGER,
            INDEX_IO,
            null,
            null,
            INDEX_MERGER_V9);
    Collection<Object[]> values = new LinkedList<>();
    for (InputRowParser parser :
        Arrays.<InputRowParser>asList(
            ROW_PARSER,
            new MapInputRowParser(
                new JSONParseSpec(
                    new TimestampSpec(TIME_COLUMN, "auto", null),
                    new DimensionsSpec(
                        DimensionsSpec.getDefaultSchemas(ImmutableList.<String>of()),
                        ImmutableList.of(DIM_FLOAT_NAME, DIM_LONG_NAME),
                        ImmutableList.<SpatialDimensionSchema>of()),
                    null,
                    null)))) {
      for (List<String> dim_names : Arrays.<List<String>>asList(null, ImmutableList.of(DIM_NAME))) {
        for (List<String> metric_names :
            Arrays.<List<String>>asList(
                null, ImmutableList.of(METRIC_LONG_NAME, METRIC_FLOAT_NAME))) {
          values.add(
              new Object[] {
                new IngestSegmentFirehoseFactory(
                    DATA_SOURCE_NAME,
                    FOREVER,
                    new SelectorDimFilter(DIM_NAME, DIM_VALUE, null),
                    dim_names,
                    metric_names,
                    Guice.createInjector(
                        new Module() {
                          @Override
                          public void configure(Binder binder) {
                            binder.bind(TaskToolboxFactory.class).toInstance(taskToolboxFactory);
                          }
                        }),
                    INDEX_IO),
                String.format(
                    "DimNames[%s]MetricNames[%s]ParserDimNames[%s]",
                    dim_names == null ? "null" : "dims",
                    metric_names == null ? "null" : "metrics",
                    parser == ROW_PARSER ? "dims" : "null"),
                parser
              });
        }
      }
    }
    return values;
  }
@RunWith(Parameterized.class)
public class IngestSegmentFirehoseFactoryTest {
  private static final ObjectMapper MAPPER;
  private static final IndexMerger INDEX_MERGER;
  private static final IndexMergerV9 INDEX_MERGER_V9;
  private static final IndexIO INDEX_IO;

  static {
    TestUtils testUtils = new TestUtils();
    MAPPER = setupInjectablesInObjectMapper(testUtils.getTestObjectMapper());
    INDEX_MERGER = testUtils.getTestIndexMerger();
    INDEX_MERGER_V9 = testUtils.getTestIndexMergerV9();
    INDEX_IO = testUtils.getTestIndexIO();
  }

  @Parameterized.Parameters(name = "{1}")
  public static Collection<Object[]> constructorFeeder() throws IOException {
    final IndexSpec indexSpec = new IndexSpec();

    final HeapMemoryTaskStorage ts = new HeapMemoryTaskStorage(new TaskStorageConfig(null) {});
    final IncrementalIndexSchema schema =
        new IncrementalIndexSchema.Builder()
            .withQueryGranularity(QueryGranularities.NONE)
            .withMinTimestamp(JodaUtils.MIN_INSTANT)
            .withDimensionsSpec(ROW_PARSER)
            .withMetrics(
                new AggregatorFactory[] {
                  new LongSumAggregatorFactory(METRIC_LONG_NAME, DIM_LONG_NAME),
                  new DoubleSumAggregatorFactory(METRIC_FLOAT_NAME, DIM_FLOAT_NAME)
                })
            .build();
    final OnheapIncrementalIndex index =
        new OnheapIncrementalIndex(schema, true, MAX_ROWS * MAX_SHARD_NUMBER);

    for (Integer i = 0; i < MAX_ROWS; ++i) {
      index.add(ROW_PARSER.parse(buildRow(i.longValue())));
    }

    if (!persistDir.mkdirs() && !persistDir.exists()) {
      throw new IOException(
          String.format("Could not create directory at [%s]", persistDir.getAbsolutePath()));
    }
    INDEX_MERGER.persist(index, persistDir, indexSpec);

    final TaskLockbox tl = new TaskLockbox(ts);
    final IndexerSQLMetadataStorageCoordinator mdc =
        new IndexerSQLMetadataStorageCoordinator(null, null, null) {
          private final Set<DataSegment> published = Sets.newHashSet();
          private final Set<DataSegment> nuked = Sets.newHashSet();

          @Override
          public List<DataSegment> getUsedSegmentsForInterval(String dataSource, Interval interval)
              throws IOException {
            return ImmutableList.copyOf(segmentSet);
          }

          @Override
          public List<DataSegment> getUsedSegmentsForIntervals(
              String dataSource, List<Interval> interval) throws IOException {
            return ImmutableList.copyOf(segmentSet);
          }

          @Override
          public List<DataSegment> getUnusedSegmentsForInterval(
              String dataSource, Interval interval) {
            return ImmutableList.of();
          }

          @Override
          public Set<DataSegment> announceHistoricalSegments(Set<DataSegment> segments) {
            Set<DataSegment> added = Sets.newHashSet();
            for (final DataSegment segment : segments) {
              if (published.add(segment)) {
                added.add(segment);
              }
            }

            return ImmutableSet.copyOf(added);
          }

          @Override
          public void deleteSegments(Set<DataSegment> segments) {
            nuked.addAll(segments);
          }
        };
    final LocalTaskActionClientFactory tac =
        new LocalTaskActionClientFactory(ts, new TaskActionToolbox(tl, mdc, newMockEmitter()));
    SegmentHandoffNotifierFactory notifierFactory =
        EasyMock.createNiceMock(SegmentHandoffNotifierFactory.class);
    EasyMock.replay(notifierFactory);

    final TaskToolboxFactory taskToolboxFactory =
        new TaskToolboxFactory(
            new TaskConfig(tmpDir.getAbsolutePath(), null, null, 50000, null, false, null, null),
            tac,
            newMockEmitter(),
            new DataSegmentPusher() {
              @Deprecated
              @Override
              public String getPathForHadoop(String dataSource) {
                return getPathForHadoop();
              }

              @Override
              public String getPathForHadoop() {
                throw new UnsupportedOperationException();
              }

              @Override
              public DataSegment push(File file, DataSegment segment) throws IOException {
                return segment;
              }
            },
            new DataSegmentKiller() {
              @Override
              public void kill(DataSegment segments) throws SegmentLoadingException {}
            },
            new DataSegmentMover() {
              @Override
              public DataSegment move(DataSegment dataSegment, Map<String, Object> targetLoadSpec)
                  throws SegmentLoadingException {
                return dataSegment;
              }
            },
            new DataSegmentArchiver() {
              @Override
              public DataSegment archive(DataSegment segment) throws SegmentLoadingException {
                return segment;
              }

              @Override
              public DataSegment restore(DataSegment segment) throws SegmentLoadingException {
                return segment;
              }
            },
            null, // segment announcer
            notifierFactory,
            null, // query runner factory conglomerate corporation unionized collective
            null, // query executor service
            null, // monitor scheduler
            new SegmentLoaderFactory(
                new SegmentLoaderLocalCacheManager(
                    null,
                    new SegmentLoaderConfig() {
                      @Override
                      public List<StorageLocationConfig> getLocations() {
                        return Lists.newArrayList();
                      }
                    },
                    MAPPER)),
            MAPPER,
            INDEX_MERGER,
            INDEX_IO,
            null,
            null,
            INDEX_MERGER_V9);
    Collection<Object[]> values = new LinkedList<>();
    for (InputRowParser parser :
        Arrays.<InputRowParser>asList(
            ROW_PARSER,
            new MapInputRowParser(
                new JSONParseSpec(
                    new TimestampSpec(TIME_COLUMN, "auto", null),
                    new DimensionsSpec(
                        DimensionsSpec.getDefaultSchemas(ImmutableList.<String>of()),
                        ImmutableList.of(DIM_FLOAT_NAME, DIM_LONG_NAME),
                        ImmutableList.<SpatialDimensionSchema>of()),
                    null,
                    null)))) {
      for (List<String> dim_names : Arrays.<List<String>>asList(null, ImmutableList.of(DIM_NAME))) {
        for (List<String> metric_names :
            Arrays.<List<String>>asList(
                null, ImmutableList.of(METRIC_LONG_NAME, METRIC_FLOAT_NAME))) {
          values.add(
              new Object[] {
                new IngestSegmentFirehoseFactory(
                    DATA_SOURCE_NAME,
                    FOREVER,
                    new SelectorDimFilter(DIM_NAME, DIM_VALUE, null),
                    dim_names,
                    metric_names,
                    Guice.createInjector(
                        new Module() {
                          @Override
                          public void configure(Binder binder) {
                            binder.bind(TaskToolboxFactory.class).toInstance(taskToolboxFactory);
                          }
                        }),
                    INDEX_IO),
                String.format(
                    "DimNames[%s]MetricNames[%s]ParserDimNames[%s]",
                    dim_names == null ? "null" : "dims",
                    metric_names == null ? "null" : "metrics",
                    parser == ROW_PARSER ? "dims" : "null"),
                parser
              });
        }
      }
    }
    return values;
  }

  public static ObjectMapper setupInjectablesInObjectMapper(ObjectMapper objectMapper) {
    objectMapper.registerModule(
        new SimpleModule("testModule").registerSubtypes(LocalLoadSpec.class));

    final GuiceAnnotationIntrospector guiceIntrospector = new GuiceAnnotationIntrospector();
    objectMapper.setAnnotationIntrospectors(
        new AnnotationIntrospectorPair(
            guiceIntrospector, objectMapper.getSerializationConfig().getAnnotationIntrospector()),
        new AnnotationIntrospectorPair(
            guiceIntrospector,
            objectMapper.getDeserializationConfig().getAnnotationIntrospector()));
    objectMapper.setInjectableValues(
        new GuiceInjectableValues(
            GuiceInjectors.makeStartupInjectorWithModules(
                ImmutableList.of(
                    new Module() {
                      @Override
                      public void configure(Binder binder) {
                        binder.bind(LocalDataSegmentPuller.class);
                      }
                    }))));
    return objectMapper;
  }

  public IngestSegmentFirehoseFactoryTest(
      IngestSegmentFirehoseFactory factory, String testName, InputRowParser rowParser) {
    this.factory = factory;
    this.rowParser = rowParser;
  }

  private static final Logger log = new Logger(IngestSegmentFirehoseFactoryTest.class);
  private static final Interval FOREVER =
      new Interval(JodaUtils.MIN_INSTANT, JodaUtils.MAX_INSTANT);
  private static final String DATA_SOURCE_NAME = "testDataSource";
  private static final String DATA_SOURCE_VERSION = "version";
  private static final Integer BINARY_VERSION = -1;
  private static final String DIM_NAME = "testDimName";
  private static final String DIM_VALUE = "testDimValue";
  private static final String DIM_LONG_NAME = "testDimLongName";
  private static final String DIM_FLOAT_NAME = "testDimFloatName";
  private static final String METRIC_LONG_NAME = "testLongMetric";
  private static final String METRIC_FLOAT_NAME = "testFloatMetric";
  private static final Long METRIC_LONG_VALUE = 1L;
  private static final Float METRIC_FLOAT_VALUE = 1.0f;
  private static final String TIME_COLUMN = "ts";
  private static final Integer MAX_SHARD_NUMBER = 10;
  private static final Integer MAX_ROWS = 10;
  private static final File tmpDir = Files.createTempDir();
  private static final File persistDir =
      Paths.get(tmpDir.getAbsolutePath(), "indexTestMerger").toFile();
  private static final List<DataSegment> segmentSet = new ArrayList<>(MAX_SHARD_NUMBER);

  private final IngestSegmentFirehoseFactory factory;
  private final InputRowParser rowParser;

  private static final InputRowParser<Map<String, Object>> ROW_PARSER =
      new MapInputRowParser(
          new JSONParseSpec(
              new TimestampSpec(TIME_COLUMN, "auto", null),
              new DimensionsSpec(
                  DimensionsSpec.getDefaultSchemas(ImmutableList.of(DIM_NAME)),
                  ImmutableList.of(DIM_FLOAT_NAME, DIM_LONG_NAME),
                  ImmutableList.<SpatialDimensionSchema>of()),
              null,
              null));

  private static Map<String, Object> buildRow(Long ts) {
    return ImmutableMap.<String, Object>of(
        TIME_COLUMN, ts,
        DIM_NAME, DIM_VALUE,
        DIM_FLOAT_NAME, METRIC_FLOAT_VALUE,
        DIM_LONG_NAME, METRIC_LONG_VALUE);
  }

  private static DataSegment buildSegment(Integer shardNumber) {
    Preconditions.checkArgument(shardNumber < MAX_SHARD_NUMBER);
    Preconditions.checkArgument(shardNumber >= 0);
    return new DataSegment(
        DATA_SOURCE_NAME,
        FOREVER,
        DATA_SOURCE_VERSION,
        ImmutableMap.<String, Object>of("type", "local", "path", persistDir.getAbsolutePath()),
        ImmutableList.of(DIM_NAME),
        ImmutableList.of(METRIC_LONG_NAME, METRIC_FLOAT_NAME),
        new NumberedShardSpec(shardNumber, MAX_SHARD_NUMBER),
        BINARY_VERSION,
        0L);
  }

  @BeforeClass
  public static void setUpStatic() throws IOException {
    for (int i = 0; i < MAX_SHARD_NUMBER; ++i) {
      segmentSet.add(buildSegment(i));
    }
  }

  @AfterClass
  public static void tearDownStatic() {
    recursivelyDelete(tmpDir);
  }

  private static void recursivelyDelete(final File dir) {
    if (dir != null) {
      if (dir.isDirectory()) {
        final File[] files = dir.listFiles();
        if (files != null) {
          for (File file : files) {
            recursivelyDelete(file);
          }
        }
      } else {
        if (!dir.delete()) {
          log.warn("Could not delete file at [%s]", dir.getAbsolutePath());
        }
      }
    }
  }

  @Test
  public void sanityTest() {
    Assert.assertEquals(DATA_SOURCE_NAME, factory.getDataSource());
    if (factory.getDimensions() != null) {
      Assert.assertArrayEquals(new String[] {DIM_NAME}, factory.getDimensions().toArray());
    }
    Assert.assertEquals(FOREVER, factory.getInterval());
    if (factory.getMetrics() != null) {
      Assert.assertEquals(
          ImmutableSet.of(METRIC_LONG_NAME, METRIC_FLOAT_NAME),
          ImmutableSet.copyOf(factory.getMetrics()));
    }
  }

  @Test
  public void simpleFirehoseReadingTest() throws IOException {
    Assert.assertEquals(MAX_SHARD_NUMBER.longValue(), segmentSet.size());
    Integer rowcount = 0;
    try (final IngestSegmentFirehose firehose =
        (IngestSegmentFirehose) factory.connect(rowParser)) {
      while (firehose.hasMore()) {
        InputRow row = firehose.nextRow();
        Assert.assertArrayEquals(new String[] {DIM_NAME}, row.getDimensions().toArray());
        Assert.assertArrayEquals(new String[] {DIM_VALUE}, row.getDimension(DIM_NAME).toArray());
        Assert.assertEquals(METRIC_LONG_VALUE.longValue(), row.getLongMetric(METRIC_LONG_NAME));
        Assert.assertEquals(
            METRIC_FLOAT_VALUE, row.getFloatMetric(METRIC_FLOAT_NAME), METRIC_FLOAT_VALUE * 0.0001);
        ++rowcount;
      }
    }
    Assert.assertEquals((int) MAX_SHARD_NUMBER * MAX_ROWS, (int) rowcount);
  }

  private static ServiceEmitter newMockEmitter() {
    return new ServiceEmitter(null, null, null) {
      @Override
      public void emit(Event event) {}

      @Override
      public void emit(ServiceEventBuilder builder) {}
    };
  }
}