private static void checkDataFromFrontToEnd(
      Map<String, List<TimeValuePair>> sourceData, List<TsFileResource> mergedFiles)
      throws IOException, IllegalPathException {
    Map<String, List<TimeValuePair>> mergedData = new HashMap<>();
    Map<String, Long> fullPathPointNum = new HashMap<>();
    for (TsFileResource mergedFile : mergedFiles) {
      try (TsFileSequenceReader reader = new TsFileSequenceReader(mergedFile.getTsFilePath())) {
        // Sequential reading of one ChunkGroup now follows this order:
        // first the CHUNK_GROUP_HEADER, then SeriesChunks (headers and data) in one ChunkGroup
        // Because we do not know how many chunks a ChunkGroup may have, we should read one byte
        // (the
        // marker) ahead and judge accordingly.
        IDeviceID device = IDeviceID.Factory.DEFAULT_FACTORY.create("");
        String measurementID = "";
        List<TimeValuePair> currTimeValuePairs = new ArrayList<>();
        reader.position((long) TSFileConfig.MAGIC_STRING.getBytes().length + 1);
        byte marker;
        while ((marker = reader.readMarker()) != MetaMarker.SEPARATOR) {
          switch (marker) {
            case MetaMarker.CHUNK_HEADER:
            case MetaMarker.TIME_CHUNK_HEADER:
            case MetaMarker.VALUE_CHUNK_HEADER:
            case MetaMarker.ONLY_ONE_PAGE_CHUNK_HEADER:
            case MetaMarker.ONLY_ONE_PAGE_TIME_CHUNK_HEADER:
            case MetaMarker.ONLY_ONE_PAGE_VALUE_CHUNK_HEADER:
              ChunkHeader header = reader.readChunkHeader(marker);
              // read the next measurement and pack data of last measurement
              if (currTimeValuePairs.size() > 0) {
                PartialPath path = new MeasurementPath(device, measurementID);
                List<TimeValuePair> timeValuePairs =
                    mergedData.computeIfAbsent(path.getFullPath(), k -> new ArrayList<>());
                timeValuePairs.addAll(currTimeValuePairs);
                currTimeValuePairs.clear();
              }
              measurementID = header.getMeasurementID();
              Decoder defaultTimeDecoder =
                  Decoder.getDecoderByType(
                      TSEncoding.valueOf(
                          TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
                      TSDataType.INT64);
              Decoder valueDecoder =
                  Decoder.getDecoderByType(header.getEncodingType(), header.getDataType());
              int dataSize = header.getDataSize();
              while (dataSize > 0) {
                valueDecoder.reset();
                PageHeader pageHeader =
                    reader.readPageHeader(
                        header.getDataType(), header.getChunkType() == MetaMarker.CHUNK_HEADER);
                PartialPath path = new MeasurementPath(device, measurementID);
                ByteBuffer pageData = reader.readPage(pageHeader, header.getCompressionType());
                PageReader reader1 =
                    new PageReader(
                        pageData, header.getDataType(), valueDecoder, defaultTimeDecoder);
                BatchData batchData = reader1.getAllSatisfiedPageData();
                long count = fullPathPointNum.getOrDefault(path.getFullPath(), 0L);
                if (header.getChunkType() == MetaMarker.CHUNK_HEADER) {
                  count += pageHeader.getNumOfValues();
                } else {
                  count += batchData.length();
                }
                fullPathPointNum.put(path.getFullPath(), count);
                IBatchDataIterator batchDataIterator = batchData.getBatchDataIterator();
                while (batchDataIterator.hasNext()) {
                  currTimeValuePairs.add(
                      new TimeValuePair(
                          batchDataIterator.currentTime(),
                          TsPrimitiveType.getByType(
                              header.getDataType(), batchDataIterator.currentValue())));
                  batchDataIterator.next();
                }
                dataSize -= pageHeader.getSerializedPageSize();
              }
              break;
            case MetaMarker.CHUNK_GROUP_HEADER:
              ChunkGroupHeader chunkGroupHeader = reader.readChunkGroupHeader();
              // read the next measurement and pack data of last device
              if (currTimeValuePairs.size() > 0) {
                PartialPath path = new MeasurementPath(device, measurementID);
                List<TimeValuePair> timeValuePairs =
                    mergedData.computeIfAbsent(path.getFullPath(), k -> new ArrayList<>());
                timeValuePairs.addAll(currTimeValuePairs);
                currTimeValuePairs.clear();
              }
              device = chunkGroupHeader.getDeviceID();
              break;
            case MetaMarker.OPERATION_INDEX_RANGE:
              reader.readPlanIndex();
              break;
            default:
              MetaMarker.handleUnexpectedMarker(marker);
          }
        }
        // pack data of last measurement
        if (currTimeValuePairs.size() > 0) {
          PartialPath path = new MeasurementPath(device, measurementID);
          List<TimeValuePair> timeValuePairs =
              mergedData.computeIfAbsent(path.getFullPath(), k -> new ArrayList<>());
          timeValuePairs.addAll(currTimeValuePairs);
        }
      }

      Collection<ModEntry> modifications =
          ModificationFile.getExclusiveMods(mergedFile).getAllMods();
      for (ModEntry modification : modifications) {
        TreeDeletionEntry deletion = (TreeDeletionEntry) modification;
        if (mergedData.containsKey(deletion.getPathPattern().getFullPath())) {
          long deletedCount = 0L;
          Iterator<TimeValuePair> timeValuePairIterator =
              mergedData.get(deletion.getPathPattern().getFullPath()).iterator();
          while (timeValuePairIterator.hasNext()) {
            TimeValuePair timeValuePair = timeValuePairIterator.next();
            if (timeValuePair.getTimestamp() >= deletion.getStartTime()
                && timeValuePair.getTimestamp() <= deletion.getEndTime()) {
              timeValuePairIterator.remove();
              deletedCount++;
            }
          }
          long count = fullPathPointNum.get(deletion.getPathPattern().getFullPath());
          count = count - deletedCount;
          fullPathPointNum.put(deletion.getPathPattern().getFullPath(), count);
        }
      }
    }
    compareSensorAndData(sourceData, mergedData);
    for (Entry<String, List<TimeValuePair>> sourceDataEntry : sourceData.entrySet()) {
      String fullPath = sourceDataEntry.getKey();
      long sourceDataNum = sourceDataEntry.getValue().size();
      long targetDataNum = fullPathPointNum.getOrDefault(fullPath, 0L);
      assertEquals(sourceDataNum, targetDataNum);
    }
  }
1. Use TsFileSequenceReader to read the merged file from back to front, and compare it with the
data before the merge to ensure complete consistency. 2. Use TsFileSequenceReader to read the
merged file from the front to the back, and compare it with the data before the merge to ensure
complete consistency (including pointsInPage statistics in pageHeader). 3. Check whether the
statistical information of TsFileResource corresponds to the original data point one-to-one,
including startTime, endTime (device level)

@param sourceData All time series before merging and their corresponding data points, fullPath
    -> pointList
@param mergedFiles The merged Files to be checked
iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/compaction/utils/CompactionCheckerUtils.java