FastqSyncTest.scala 9.59 KB
Newer Older
bow's avatar
bow committed
1
2
3
4
5
6
/**
 * Copyright (c) 2014 Leiden University Medical Center - Sequencing Analysis Support Core <sasc@lumc.nl>
 * @author Wibowo Arindrarto <w.arindrarto@lumc.nl>
 */
package nl.lumc.sasc.biopet.tools

bow's avatar
bow committed
7
8
9
10
import java.io.File
import java.nio.file.Paths
import scala.collection.JavaConverters._

11
import htsjdk.samtools.fastq.{ AsyncFastqWriter, FastqReader, FastqRecord }
bow's avatar
bow committed
12
import org.mockito.Mockito.{ inOrder => inOrd, when }
bow's avatar
bow committed
13
14
15
import org.scalatest.Matchers
import org.scalatest.mock.MockitoSugar
import org.scalatest.testng.TestNGSuite
bow's avatar
bow committed
16
import org.testng.annotations.{ DataProvider, Test }
bow's avatar
bow committed
17
18
19

class FastqSyncTest extends TestNGSuite with MockitoSugar with Matchers {

bow's avatar
bow committed
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  import FastqSync._

  private def resourceFile(p: String): File =
    new File(resourcePath(p))

  private def resourcePath(p: String): String =
    Paths.get(getClass.getResource(p).toURI).toString

  // Helper functions to create iterator over FastqRecords given its IDs as Ints
  private def recordsOver(ids: String*): java.util.Iterator[FastqRecord] = ids
    .map(x => new FastqRecord(x, "A", "", "H"))
    .toIterator.asJava

  @DataProvider(name = "mockReaderProvider")
  def mockReaderProvider() =
    Array(
      Array(mock[FastqReader], mock[FastqReader], mock[FastqReader]))

  @Test(dataProvider = "mockReaderProvider")
  def testDefault(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1", "2", "3")
    when(aMock.iterator) thenReturn recordsOver("1", "2", "3")
    when(bMock.iterator) thenReturn recordsOver("1", "2", "3")

44
45
46
47
48
49
50
51
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 3
    sync(0) shouldBe (new FastqRecord("1", "A", "", "H"), new FastqRecord("1", "A", "", "H"))
    sync(1) shouldBe (new FastqRecord("2", "A", "", "H"), new FastqRecord("2", "A", "", "H"))
    sync(2) shouldBe (new FastqRecord("3", "A", "", "H"), new FastqRecord("3", "A", "", "H"))
    counts.numDiscard1 shouldBe 0
    counts.numDiscard2 shouldBe 0
    counts.numKept shouldBe 3
bow's avatar
bow committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
  }

  @Test(dataProvider = "mockReaderProvider")
  def testRefTooShort(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1", "2")
    when(aMock.iterator) thenReturn recordsOver("1", "2", "3")
    when(bMock.iterator) thenReturn recordsOver("1", "2", "3")

    val thrown = intercept[NoSuchElementException] {
      syncFastq(refMock, aMock, bMock)
    }
    thrown.getMessage should ===("Reference record stream shorter than expected")
  }

  @Test(dataProvider = "mockReaderProvider")
  def testSeqAEmpty(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1", "2", "3")
    when(aMock.iterator) thenReturn recordsOver()
    when(bMock.iterator) thenReturn recordsOver("1", "2", "3")

72
73
74
75
76
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 0
    counts.numDiscard1 shouldBe 0
    counts.numDiscard2 shouldBe 3
    counts.numKept shouldBe 0
bow's avatar
bow committed
77
78
79
80
81
82
83
84
  }

  @Test(dataProvider = "mockReaderProvider")
  def testSeqBEmpty(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1", "2", "3")
    when(aMock.iterator) thenReturn recordsOver("1", "2", "3")
    when(bMock.iterator) thenReturn recordsOver()

85
86
87
88
89
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 0
    counts.numDiscard1 shouldBe 3
    counts.numDiscard2 shouldBe 0
    counts.numKept shouldBe 0
bow's avatar
bow committed
90
91
92
93
94
95
96
97
  }

  @Test(dataProvider = "mockReaderProvider")
  def testSeqAShorter(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1", "2", "3")
    when(aMock.iterator) thenReturn recordsOver("2", "3")
    when(bMock.iterator) thenReturn recordsOver("1", "2", "3")

98
99
100
101
102
103
104
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 2
    sync(0) shouldBe (new FastqRecord("2", "A", "", "H"), new FastqRecord("2", "A", "", "H"))
    sync(1) shouldBe (new FastqRecord("3", "A", "", "H"), new FastqRecord("3", "A", "", "H"))
    counts.numDiscard1 shouldBe 0
    counts.numDiscard2 shouldBe 1
    counts.numKept shouldBe 2
bow's avatar
bow committed
105
106
107
108
109
110
111
112
  }

  @Test(dataProvider = "mockReaderProvider")
  def testSeqBShorter(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1", "2", "3")
    when(aMock.iterator) thenReturn recordsOver("2", "3")
    when(bMock.iterator) thenReturn recordsOver("1", "2", "3")

113
114
115
116
117
118
119
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 2
    sync(0) shouldBe (new FastqRecord("2", "A", "", "H"), new FastqRecord("2", "A", "", "H"))
    sync(1) shouldBe (new FastqRecord("3", "A", "", "H"), new FastqRecord("3", "A", "", "H"))
    counts.numDiscard1 shouldBe 0
    counts.numDiscard2 shouldBe 1
    counts.numKept shouldBe 2
bow's avatar
bow committed
120
121
122
123
124
125
126
127
  }

  @Test(dataProvider = "mockReaderProvider")
  def testSeqABShorter(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1", "2", "3")
    when(aMock.iterator) thenReturn recordsOver("2", "3")
    when(bMock.iterator) thenReturn recordsOver("1", "2")

128
129
130
131
132
133
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 1
    sync(0) shouldBe (new FastqRecord("2", "A", "", "H"), new FastqRecord("2", "A", "", "H"))
    counts.numDiscard1 shouldBe 1
    counts.numDiscard2 shouldBe 1
    counts.numKept shouldBe 1
bow's avatar
bow committed
134
135
136
137
138
139
140
141
  }

  @Test(dataProvider = "mockReaderProvider")
  def testSeqABShorterPairMarkSlash(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1/1", "2/1", "3/1")
    when(aMock.iterator) thenReturn recordsOver("2/1", "3/1")
    when(bMock.iterator) thenReturn recordsOver("1/2", "2/2")

142
143
144
145
146
147
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 1
    sync(0) shouldBe (new FastqRecord("2/1", "A", "", "H"), new FastqRecord("2/2", "A", "", "H"))
    counts.numDiscard1 shouldBe 1
    counts.numDiscard2 shouldBe 1
    counts.numKept shouldBe 1
bow's avatar
bow committed
148
149
150
151
152
153
154
155
  }

  @Test(dataProvider = "mockReaderProvider")
  def testSeqABShorterPairMarkUnderscore(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1_1", "2_1", "3_1")
    when(aMock.iterator) thenReturn recordsOver("2_1", "3_1")
    when(bMock.iterator) thenReturn recordsOver("1_2", "2_2")

156
157
158
159
160
161
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 1
    sync(0) shouldBe (new FastqRecord("2_1", "A", "", "H"), new FastqRecord("2_2", "A", "", "H"))
    counts.numDiscard1 shouldBe 1
    counts.numDiscard2 shouldBe 1
    counts.numKept shouldBe 1
bow's avatar
bow committed
162
163
164
165
166
167
168
169
  }

  @Test(dataProvider = "mockReaderProvider")
  def testSeqABShorterWithDesc(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1 desc1b", "2 desc2b", "3 desc3b")
    when(aMock.iterator) thenReturn recordsOver("2 desc2a", "3 desc3a")
    when(bMock.iterator) thenReturn recordsOver("1 desc1b", "2 desc2b")

170
171
172
173
174
175
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 1
    sync(0) shouldBe (new FastqRecord("2 desc2a", "A", "", "H"), new FastqRecord("2 desc2b", "A", "", "H"))
    counts.numDiscard1 shouldBe 1
    counts.numDiscard2 shouldBe 1
    counts.numKept shouldBe 1
bow's avatar
bow committed
176
177
178
179
180
181
182
183
  }

  @Test(dataProvider = "mockReaderProvider")
  def testComplex(refMock: FastqReader, aMock: FastqReader, bMock: FastqReader) = {
    when(refMock.iterator) thenReturn recordsOver("1/2 yep", "2/2 yep", "3/2 yep", "4/2 yep", "5/2 yep")
    when(aMock.iterator) thenReturn recordsOver("1/1 yep", "2/1 yep", "4/1 yep")
    when(bMock.iterator) thenReturn recordsOver("1/2 yep", "3/2 yep", "4/2 yep")

184
185
186
187
188
189
190
    val (sync, counts) = syncFastq(refMock, aMock, bMock)
    sync.length shouldBe 2
    sync(0) shouldBe (new FastqRecord("1/1 yep", "A", "", "H"), new FastqRecord("1/2 yep", "A", "", "H"))
    sync(1) shouldBe (new FastqRecord("4/1 yep", "A", "", "H"), new FastqRecord("4/2 yep", "A", "", "H"))
    counts.numDiscard1 shouldBe 1
    counts.numDiscard2 shouldBe 1
    counts.numKept shouldBe 2
bow's avatar
bow committed
191
192
193
  }

  @Test def testWriteSynced() = {
194
195
    val aMock = mock[AsyncFastqWriter]
    val bMock = mock[AsyncFastqWriter]
196
    val sync = Stream(
bow's avatar
bow committed
197
      (new FastqRecord("1", "A", "", "H"), new FastqRecord("1", "T", "", "E")),
198
199
      (new FastqRecord("2", "A", "", "H"), new FastqRecord("2", "T", "", "E")))
    val counts = SyncCounts(4, 3, 2)
bow's avatar
bow committed
200
201
202
    val obs = inOrd(aMock, bMock)
    val stdout = new java.io.ByteArrayOutputStream
    Console.withOut(stdout) {
203
      writeSyncedFastq(sync, counts, aMock, bMock)
bow's avatar
bow committed
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
    }
    stdout.toString should ===(List(
      "Filtered 4 reads from first read file.",
      "Filtered 3 reads from second read file.",
      "Synced read files contain 2 reads.\n"
    ).mkString("\n"))
    obs.verify(aMock).write(new FastqRecord("1", "A", "", "H"))
    obs.verify(bMock).write(new FastqRecord("1", "T", "", "E"))
    obs.verify(aMock).write(new FastqRecord("2", "A", "", "H"))
    obs.verify(bMock).write(new FastqRecord("2", "T", "", "E"))
  }

  @Test def testArgsMinimum() = {
    val args = Array(
      "-r", resourcePath("/paired01a.fq"),
      "-i", resourcePath("/paired01a.fq"),
      "-j", resourcePath("/paired01b.fq"),
      "-o", "/tmp/mockout1.fq",
      "-p", "/tmp/mockout2.fq")
    val parsed = parseArgs(args)
    parsed.refFastq shouldBe resourceFile("/paired01a.fq")
    parsed.inputFastq1 shouldBe resourceFile("/paired01a.fq")
    parsed.inputFastq2 shouldBe resourceFile("/paired01b.fq")
    parsed.outputFastq1 shouldBe new File("/tmp/mockout1.fq")
    parsed.outputFastq2 shouldBe new File("/tmp/mockout2.fq")
  }
bow's avatar
bow committed
230
}