[tor-commits] [metrics-lib/master] Also check exception message in tests. Part of task-22280.

karsten at torproject.org karsten at torproject.org
Mon Jun 12 08:11:12 UTC 2017


commit c39a15a2ffb929ffd844d79bf2b8d0d5bdc15620
Author: iwakeh <iwakeh at torproject.org>
Date:   Thu Jun 1 08:44:28 2017 +0000

    Also check exception message in tests.  Part of task-22280.
---
 .../descriptor/impl/ExitListImplTest.java          |  13 +-
 .../impl/ExtraInfoDescriptorImplTest.java          | 379 +++++++++++----
 .../descriptor/impl/MicrodescriptorImplTest.java   |  27 +-
 .../impl/RelayNetworkStatusConsensusImplTest.java  | 517 +++++++++++++++++----
 .../impl/RelayNetworkStatusImplTest.java           |   9 +-
 .../impl/RelayNetworkStatusVoteImplTest.java       | 450 ++++++++++++++----
 .../descriptor/impl/ServerDescriptorImplTest.java  | 389 ++++++++++++----
 7 files changed, 1415 insertions(+), 369 deletions(-)

diff --git a/src/test/java/org/torproject/descriptor/impl/ExitListImplTest.java b/src/test/java/org/torproject/descriptor/impl/ExitListImplTest.java
index 916c4df..5a67d53 100644
--- a/src/test/java/org/torproject/descriptor/impl/ExitListImplTest.java
+++ b/src/test/java/org/torproject/descriptor/impl/ExitListImplTest.java
@@ -9,13 +9,18 @@ import static org.junit.Assert.assertTrue;
 import org.torproject.descriptor.DescriptorParseException;
 import org.torproject.descriptor.ExitListEntry;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import java.util.HashMap;
 import java.util.Map;
 
 public class ExitListImplTest {
 
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   @Test()
   public void testAnnotatedInput() throws Exception {
     ExitListImpl result = new ExitListImpl((tordnselAnnotation + input)
@@ -74,14 +79,18 @@ public class ExitListImplTest {
     assertTrue("Map: " + map, map.containsKey("81.7.17.173"));
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testInsufficientInput0() throws Exception {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Missing 'ExitAddress' line in exit list entry.");
     new ExitListImpl((tordnselAnnotation + insufficientInput[0])
         .getBytes("US-ASCII"), fileName, false);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testInsufficientInput1() throws Exception {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Missing 'Published' line in exit list entry.");
     new ExitListImpl((tordnselAnnotation + insufficientInput[1])
         .getBytes("US-ASCII"), fileName, false);
   }
diff --git a/src/test/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java b/src/test/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
index b745b07..e10fa7b 100644
--- a/src/test/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
+++ b/src/test/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
@@ -970,8 +970,11 @@ public class ExtraInfoDescriptorImplTest {
     assertNotNull(descriptor.getDirreqReadHistory());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExtraInfoLineMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'extra-info' is contained 0 times, but "
+        + "must be contained exactly once.");
     DescriptorBuilder.createWithExtraInfoLine(null);
   }
 
@@ -996,55 +999,81 @@ public class ExtraInfoDescriptorImplTest {
         descriptor.getFingerprint());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExtraInfoLineNotFirst()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Keyword 'extra-info' must be contained in the first line.");
     DescriptorBuilder.createWithExtraInfoLine("geoip-db-digest "
         + "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8\n"
         + "extra-info chaoscomputerclub5 "
         + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'extra-info  "
+        + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26' "
+        + "in extra-info descriptor.");
     DescriptorBuilder.createWithExtraInfoLine("extra-info  "
         + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameInvalidChar() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line 'extra-info "
+        + "chaoscomputerclub% A9C039A5FD02FCA06303DCFAABE25C5912C63B26'.");
     DescriptorBuilder.createWithExtraInfoLine("extra-info "
         + "chaoscomputerclub% A9C039A5FD02FCA06303DCFAABE25C5912C63B26");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameTooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line 'extra-info "
+        + "chaoscomputerclub5ReallyLongNickname "
+        + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26'.");
     DescriptorBuilder.createWithExtraInfoLine("extra-info "
         + "chaoscomputerclub5ReallyLongNickname "
         + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintG() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'extra-info "
+        + "chaoscomputerclub5 G9C039A5FD02FCA06303DCFAABE25C5912C63B26'.");
     DescriptorBuilder.createWithExtraInfoLine("extra-info "
         + "chaoscomputerclub5 G9C039A5FD02FCA06303DCFAABE25C5912C63B26");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintTooShort() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'extra-info "
+        + "chaoscomputerclub5 A9C039A5FD02FCA06303DCFAABE25C5912C6'.");
     DescriptorBuilder.createWithExtraInfoLine("extra-info "
         + "chaoscomputerclub5 A9C039A5FD02FCA06303DCFAABE25C5912C6");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintTooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'extra-info "
+        + "chaoscomputerclub5 A9C039A5FD02FCA06303DCFAABE25C5912C63B26A9C0'.");
     DescriptorBuilder.createWithExtraInfoLine("extra-info "
         + "chaoscomputerclub5 A9C039A5FD02FCA06303DCFAABE25C5912C63B26"
         + "A9C0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPublishedMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'published' is contained 0 times, "
+        + "but must be contained exactly once.");
     DescriptorBuilder.createWithPublishedLine(null);
   }
 
@@ -1062,9 +1091,13 @@ public class ExtraInfoDescriptorImplTest {
     assertEquals(1328951316000L, descriptor.getPublishedMillis());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWriteHistoryNegativeBytes()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Negative bandwidth values are not allowed in "
+        + "line 'write-history 2012-02-11 09:03:39 (900 s) -4713350144,"
+        + "-4723824640,-4710717440,-4572675072'.");
     DescriptorBuilder.createWithWriteHistoryLine("write-history "
         + "2012-02-11 09:03:39 (900 s) "
         + "-4713350144,-4723824640,-4710717440,-4572675072");
@@ -1086,9 +1119,13 @@ public class ExtraInfoDescriptorImplTest {
         + "4707695616,4699666432,4650004480,4489718784");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testReadHistoryNegativeInterval()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Only positive interval lengths are allowed in "
+        + "line 'read-history 2012-02-11 09:03:39 (-900 s) 4707695616,"
+        + "4699666432,4650004480,4489718784'.");
     DescriptorBuilder.createWithReadHistoryLine("read-history "
         + "2012-02-11 09:03:39 (-900 s) "
         + "4707695616,4699666432,4650004480,4489718784");
@@ -1102,17 +1139,25 @@ public class ExtraInfoDescriptorImplTest {
         + "4707695616,4699666432,4650004480,4489718784");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirreqWriteHistoryMissingBytesBegin()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid bandwidth-history line "
+        + "'dirreq-write-history 2012-02-11 09:03:39 (900 s) "
+        + ",64996352,60625920,67922944'.");
     DescriptorBuilder.createWithDirreqWriteHistoryLine(
         "dirreq-write-history 2012-02-11 09:03:39 (900 s) "
         + ",64996352,60625920,67922944");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirreqWriteHistoryMissingBytesMiddle()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid bandwidth-history line "
+        + "'dirreq-write-history 2012-02-11 09:03:39 (900 s) 81281024,,"
+        + "60625920,67922944'.");
     DescriptorBuilder.createWithDirreqWriteHistoryLine(
         "dirreq-write-history 2012-02-11 09:03:39 (900 s) "
         + "81281024,,60625920,67922944");
@@ -1126,9 +1171,13 @@ public class ExtraInfoDescriptorImplTest {
         + "67922944 bin_size=1024");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirreqReadHistoryMissingBytesEnd()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid bandwidth-history line "
+        + "'dirreq-read-history 2012-02-11 09:03:39 (900 s) "
+        + "17074176,16235520,16005120,'.");
     DescriptorBuilder.createWithDirreqReadHistoryLine(
         "dirreq-read-history 2012-02-11 09:03:39 (900 s) "
         + "17074176,16235520,16005120,");
@@ -1143,23 +1192,32 @@ public class ExtraInfoDescriptorImplTest {
         descriptor.getGeoipDbDigestSha1Hex());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipDbDigestTooShort()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'geoip-db-digest "
+        + "916A3CA8B7DF61473D5AE5B21711F35F301C'.");
     DescriptorBuilder.createWithGeoipDbDigestLine("geoip-db-digest "
         + "916A3CA8B7DF61473D5AE5B21711F35F301C");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipDbDigestIllegalChars()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'geoip-db-digest "
+        + "&%6A3CA8B7DF61473D5AE5B21711F35F301CE9E8'.");
     DescriptorBuilder.createWithGeoipDbDigestLine("geoip-db-digest "
         + "&%6A3CA8B7DF61473D5AE5B21711F35F301CE9E8");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipDbDigestMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'geoip-db-digest' in extra-info "
+        + "descriptor.");
     DescriptorBuilder.createWithGeoipDbDigestLine("geoip-db-digest");
   }
 
@@ -1206,40 +1264,59 @@ public class ExtraInfoDescriptorImplTest {
     assertFalse(ips.containsKey("pl"));
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipStartTimeDateOnly()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'geoip-start-time 2012-02-10' in "
+        + "extra-info descriptor.");
     GeoipStatsBuilder.createWithGeoipStartTimeLine("geoip-start-time "
         + "2012-02-10");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipClientOriginsDash()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'geoip-client-origins de-1152,cn=896,us=712"
+        + ",it=504,ru=352,fr=208,gb=208,ir=200' contains an illegal key in "
+        + "list element 'de-1152'.");
     GeoipStatsBuilder.createWithGeoipClientOriginsLine(
         "geoip-client-origins de-1152,cn=896,us=712,it=504,ru=352,fr=208,"
         + "gb=208,ir=200");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipClientOriginsZero()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'geoip-client-origins de=zero,cn=896,us=712"
+        + ",it=504,ru=352,fr=208,gb=208,ir=200' contains an illegal value in "
+        + "list element 'de=zero'.");
     GeoipStatsBuilder.createWithGeoipClientOriginsLine(
         "geoip-client-origins de=zero,cn=896,us=712,it=504,ru=352,fr=208,"
         + "gb=208,ir=200");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipClientOriginsNone()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'geoip-client-origins de=none,cn=896,us=712"
+        + ",it=504,ru=352,fr=208,gb=208,ir=200' contains an illegal value in "
+        + "list element 'de=none'.");
     GeoipStatsBuilder.createWithGeoipClientOriginsLine(
         "geoip-client-origins de=none,cn=896,us=712,it=504,ru=352,fr=208,"
         + "gb=208,ir=200");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipClientOriginsOther()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'geoip-client-origins de=1152,cn=896,"
+        + "us=712,it=504,ru=352,fr=208,gb=208,other=200' contains an "
+        + "illegal key in list element 'other=200'.");
     GeoipStatsBuilder.createWithGeoipClientOriginsLine(
         "geoip-client-origins de=1152,cn=896,us=712,it=504,ru=352,fr=208,"
         + "gb=208,other=200");
@@ -1261,33 +1338,49 @@ public class ExtraInfoDescriptorImplTest {
         + "GB=208,IR=200");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipClientOriginsMissingBegin()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'geoip-client-origins ,cn=896,us=712,"
+        + "it=504,ru=352,fr=208,gb=208,ir=200' "
+        + "contains an illegal key in list element ''.");
     GeoipStatsBuilder.createWithGeoipClientOriginsLine(
         "geoip-client-origins ,cn=896,us=712,it=504,ru=352,fr=208,gb=208,"
         + "ir=200");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipClientOriginsMissingMiddle()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'geoip-client-origins de=1152,,us=712,"
+        + "it=504,ru=352,fr=208,gb=208,ir=200' contains an illegal key in "
+        + "list element ''.");
     GeoipStatsBuilder.createWithGeoipClientOriginsLine(
         "geoip-client-origins de=1152,,us=712,it=504,ru=352,fr=208,"
         + "gb=208,ir=200");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipClientOriginsMissingEnd()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'geoip-client-origins de=1152,cn=896,"
+        + "us=712,it=504,ru=352,fr=208,gb=208,' contains an illegal key in "
+        + "list element ''.");
     GeoipStatsBuilder.createWithGeoipClientOriginsLine(
         "geoip-client-origins de=1152,cn=896,us=712,it=504,ru=352,fr=208,"
         + "gb=208,");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testGeoipClientOriginsDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'geoip-client-origins de=1152,de=952,"
+        + "cn=896,us=712,it=504,ru=352,fr=208,gb=208,ir=200' contains "
+        + "duplicate key 'de'.");
     GeoipStatsBuilder.createWithGeoipClientOriginsLine(
         "geoip-client-origins de=1152,de=952,cn=896,us=712,it=504,"
         + "ru=352,fr=208,gb=208,ir=200");
@@ -1346,9 +1439,12 @@ public class ExtraInfoDescriptorImplTest {
         + "2012-02-11 00:59:53 (172800 s) XXXXXXXXXXXXXXXXXXXXXXXXXXX");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirreqV3IpsThreeLetterCountry()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'dirreq-v3-ips usa=1544' contains an "
+        + "illegal key in list element 'usa=1544'.");
     DirreqStatsBuilder.createWithDirreqV3IpsLine("dirreq-v3-ips "
         + "usa=1544");
   }
@@ -1366,9 +1462,12 @@ public class ExtraInfoDescriptorImplTest {
     DirreqStatsBuilder.createWithDirreqV2IpsLine("dirreq-v2-ips 00=8");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirreqV3ReqsOneLetterCountry()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'dirreq-v3-reqs u=1744' contains an "
+        + "illegal key in list element 'u=1744'.");
     DirreqStatsBuilder.createWithDirreqV3ReqsLine("dirreq-v3-reqs "
         + "u=1744");
   }
@@ -1380,15 +1479,21 @@ public class ExtraInfoDescriptorImplTest {
     assertNull(eid.getDirreqV3Reqs());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirreqV2ReqsNoNumber()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'dirreq-v2-reqs us=' contains an illegal "
+        + "value in list element 'us='.");
     DirreqStatsBuilder.createWithDirreqV2ReqsLine("dirreq-v2-reqs us=");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirreqV3RespTwoEqualSigns()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'dirreq-v3-resp ok==10848' contains an "
+        + "illegal value in list element 'ok==10848'.");
     DirreqStatsBuilder.createWithDirreqV3RespLine("dirreq-v3-resp "
         + "ok==10848");
   }
@@ -1410,23 +1515,28 @@ public class ExtraInfoDescriptorImplTest {
         + "ok=1084 4801=ko");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testDirreqV2RespNull()
-      throws DescriptorParseException {
+  @Test
+  public void testDirreqV2RespNull() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Line 'dirreq-v2-resp ok=null' contains an illegal value in "
+        + "list element 'ok=null'.");
     DirreqStatsBuilder.createWithDirreqV2RespLine("dirreq-v2-resp "
         + "ok=null");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testDirreqV2ShareComma()
-      throws DescriptorParseException {
+  @Test
+  public void testDirreqV2ShareComma() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'dirreq-v2-share 0,37%'.");
     DirreqStatsBuilder.createWithDirreqV2ShareLine("dirreq-v2-share "
         + "0,37%");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testDirreqV3ShareNoPercent()
-      throws DescriptorParseException {
+  @Test
+  public void testDirreqV3ShareNoPercent() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'dirreq-v3-share 0.37'.");
     DirreqStatsBuilder.createWithDirreqV3ShareLine("dirreq-v3-share "
         + "0.37");
   }
@@ -1438,9 +1548,11 @@ public class ExtraInfoDescriptorImplTest {
         + "0.37% 123456");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testDirreqV3DirectDlSpace()
-      throws DescriptorParseException {
+  @Test
+  public void testDirreqV3DirectDlSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'dirreq-v3-direct-dl complete 36' contains "
+        + "an illegal value in list element 'complete'.");
     DirreqStatsBuilder.createWithDirreqV3DirectDlLine(
         "dirreq-v3-direct-dl complete 36");
   }
@@ -1452,16 +1564,20 @@ public class ExtraInfoDescriptorImplTest {
         "dirreq-v2-direct-dl complete=-8");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testDirreqV3TunneledDlTooLarge()
-      throws DescriptorParseException {
+  @Test
+  public void testDirreqV3TunneledDlTooLarge() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'dirreq-v3-tunneled-dl complete=2147483648'"
+        + " contains an illegal value in list element 'complete=2147483648'.");
     DirreqStatsBuilder.createWithDirreqV3TunneledDlLine(
         "dirreq-v3-tunneled-dl complete=2147483648");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testDirreqV3TunneledDlDouble()
-      throws DescriptorParseException {
+  @Test
+  public void testDirreqV3TunneledDlDouble() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'dirreq-v2-tunneled-dl complete=0.001' "
+        + "contains an illegal value in list element 'complete=0.001'.");
     DirreqStatsBuilder.createWithDirreqV2TunneledDlLine(
         "dirreq-v2-tunneled-dl complete=0.001");
   }
@@ -1485,15 +1601,20 @@ public class ExtraInfoDescriptorImplTest {
     assertFalse(ips.containsKey("no"));
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEntryStatsEndNoDate() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal line 'entry-stats-end 01:59:39 (86400 s)'.");
     EntryStatsBuilder.createWithEntryStatsEndLine("entry-stats-end "
         + "01:59:39 (86400 s)");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testEntryStatsIpsSemicolon()
-      throws DescriptorParseException {
+  @Test
+  public void testEntryStatsIpsSemicolon() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'entry-ips ir=25368;us=15744' contains an "
+        + "illegal value in list element 'ir=25368;us=15744'.");
     EntryStatsBuilder.createWithEntryIpsLine("entry-ips "
         + "ir=25368;us=15744");
   }
@@ -1519,37 +1640,50 @@ public class ExtraInfoDescriptorImplTest {
     assertEquals(866, descriptor.getCellCircuitsPerDecile());
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testCellStatsEndNoSeconds()
-      throws DescriptorParseException {
+  @Test
+  public void testCellStatsEndNoSeconds() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal line 'cell-stats-end 2012-02-11 01:59:39 (86400)'.");
     CellStatsBuilder.createWithCellStatsEndLine("cell-stats-end "
         + "2012-02-11 01:59:39 (86400)");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testCellProcessedCellsNineComma()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'cell-processed-cells 1441,11,6,4,2,"
+        + "1,1,1,1,' contains an illegal value in list element ''.");
     CellStatsBuilder.createWithCellProcessedCellsLine(
         "cell-processed-cells 1441,11,6,4,2,1,1,1,1,");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testCellProcessedCellsEleven()
-      throws DescriptorParseException {
+  @Test
+  public void testCellProcessedCellsEleven() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("There must be exact ten values in line "
+        + "'cell-queued-cells 3.29,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,"
+        + "0.00,0.00'.");
     CellStatsBuilder.createWithCellQueuedCellsLine("cell-queued-cells "
         + "3.29,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testCellTimeInQueueDouble()
-      throws DescriptorParseException {
+  @Test
+  public void testCellTimeInQueueDouble() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'cell-time-in-queue 524.0,1.0,1.0,0.0,0.0,"
+        + "25.0,0.0,0.0,0.0,0.0' contains an illegal value in list element "
+        + "'524.0'.");
     CellStatsBuilder.createWithCellTimeInQueueLine("cell-time-in-queue "
         + "524.0,1.0,1.0,0.0,0.0,25.0,0.0,0.0,0.0,0.0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testCellCircuitsPerDecileNegative()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'cell-circuits-per-decile -866'.");
     CellStatsBuilder.createWithCellCircuitsPerDecileLine(
         "cell-circuits-per-decile -866");
   }
@@ -1576,9 +1710,11 @@ public class ExtraInfoDescriptorImplTest {
     assertEquals(1744, descriptor.getConnBiDirectBoth());
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testConnBiDirectStatsFive()
-      throws DescriptorParseException {
+  @Test
+  public void testConnBiDirectStatsFive() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'conn-bi-direct 2012-02-11 01:59:39"
+        + " (86400 s) 42173,1591,1310,1744,42' in extra-info descriptor.");
     DescriptorBuilder.createWithConnBiDirectLine("conn-bi-direct "
         + "2012-02-11 01:59:39 (86400 s) 42173,1591,1310,1744,42");
   }
@@ -1624,23 +1760,30 @@ public class ExtraInfoDescriptorImplTest {
     }
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testExitStatsEndNoSeconds()
-      throws DescriptorParseException {
+  @Test
+  public void testExitStatsEndNoSeconds() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'exit-stats-end 2012-02-11 01:59 (86400 s)'.");
     ExitStatsBuilder.createWithExitStatsEndLine("exit-stats-end "
         + "2012-02-11 01:59 (86400 s)");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExitStatsWrittenNegativePort()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Invalid port in line 'exit-kibibytes-written -25=74647'.");
     ExitStatsBuilder.createWithExitKibibytesWrittenLine(
         "exit-kibibytes-written -25=74647");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testExitStatsWrittenUnknown()
-      throws DescriptorParseException {
+  @Test
+  public void testExitStatsWrittenUnknown() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Invalid port in line 'exit-kibibytes-written unknown=74647'.");
     ExitStatsBuilder.createWithExitKibibytesWrittenLine(
         "exit-kibibytes-written unknown=74647");
   }
@@ -1655,9 +1798,12 @@ public class ExtraInfoDescriptorImplTest {
         "exit-kibibytes-written =74647");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExitStatsReadNegativeBytes()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Invalid value in line 'exit-kibibytes-read 25=-35562'.");
     ExitStatsBuilder.createWithExitKibibytesReadLine(
         "exit-kibibytes-read 25=-35562");
   }
@@ -1705,9 +1851,12 @@ public class ExtraInfoDescriptorImplTest {
     assertEquals(1728, trans.get("obfs3").intValue());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBridgeStatsEndIntervalZero()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Interval length must be positive in line "
+        + "'bridge-stats-end 2012-02-11 01:59:39 (0 s)'.");
     BridgeStatsBuilder.createWithBridgeStatsEndLine("bridge-stats-end "
         + "2012-02-11 01:59:39 (0 s)");
   }
@@ -1719,15 +1868,21 @@ public class ExtraInfoDescriptorImplTest {
         + "2012-02-11 01:59:39 (86400 s) 99999999999999999999999999999999");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBridgeIpsDouble()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'bridge-ips ir=24.5' contains an illegal "
+        + "value in list element 'ir=24.5'.");
     BridgeStatsBuilder.createWithBridgeIpsLine("bridge-ips ir=24.5");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBridgeIpsNonAsciiKeyword()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized character in keyword "
+        + "'�bridge-ips' in line '�bridge-ips'.");
     DescriptorBuilder.createWithNonAsciiLineBytes(new byte[] {
         0x14, (byte) 0xfe, 0x18,                  // non-ascii chars
         0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2d, // "bridge-"
@@ -1740,9 +1895,12 @@ public class ExtraInfoDescriptorImplTest {
     BridgeStatsBuilder.createWithBridgeIpsLine("bridge-ips ir=24 5");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBridgeIpVersionsDouble()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'bridge-ip-versions v4=24.5' "
+        + "contains an illegal value in list element 'v4=24.5'.");
     BridgeStatsBuilder.createWithBridgeIpVersionsLine(
         "bridge-ip-versions v4=24.5");
   }
@@ -1754,9 +1912,12 @@ public class ExtraInfoDescriptorImplTest {
         "bridge-ip-versions v4=24 5");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBridgeIpTransportsDouble()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'bridge-ip-transports obfs2=24.5' contains "
+        + "an illegal value in list element 'obfs2=24.5'.");
     BridgeStatsBuilder.createWithBridgeIpTransportsLine(
         "bridge-ip-transports obfs2=24.5");
   }
@@ -1837,8 +1998,11 @@ public class ExtraInfoDescriptorImplTest {
             0L, 0L, 0L, 0L, 0L, 42L});
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPaddingCountsNoTime() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'padding-counts 2017-05-10"
+        + " (86400 s) bin-size=10000 write-drop=10000'.");
     DescriptorBuilder.createWithPaddingCountsLine("padding-counts 2017-05-10 "
         + "(86400 s) bin-size=10000 write-drop=10000");
   }
@@ -1954,16 +2118,24 @@ public class ExtraInfoDescriptorImplTest {
         descriptor.getHidservRendRelayedCellsParameters().isEmpty());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testHidservDirOnionsSeenCommaSeparatedParams()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'hidserv-dir-onions-seen -3 delta_f=8,"
+        + "epsilon=0.30,bin_size=8' contains an illegal value in list element "
+        + "'delta_f=8,epsilon=0.30,bin_size=8'.");
     HidservStatsBuilder.createWithHidservDirOnionsSeenLine(
         "hidserv-dir-onions-seen -3 delta_f=8,epsilon=0.30,bin_size=8");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testHidservDirOnionsSeenNoDoubleParams()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'hidserv-dir-onions-seen -3 delta_f=A "
+        + "epsilon=B bin_size=C' contains an illegal value in list element "
+        + "'delta_f=A'.");
     HidservStatsBuilder.createWithHidservDirOnionsSeenLine(
         "hidserv-dir-onions-seen -3 delta_f=A epsilon=B bin_size=C");
   }
@@ -1975,9 +2147,12 @@ public class ExtraInfoDescriptorImplTest {
         "hidserv-dir-onions-seen -3 delta_f=8 epsilon=0.30 bin_size=8 pi=3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testRouterSignatureNotLastLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'published' is contained 2 times, "
+        + "but must be contained exactly once.");
     DescriptorBuilder.createWithRouterSignatureLines("router-signature\n"
         + "-----BEGIN SIGNATURE-----\n"
         + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT"
@@ -1986,9 +2161,11 @@ public class ExtraInfoDescriptorImplTest {
         + "-----END SIGNATURE-----\npublished 2012-02-11 09:08:36");
   }
 
-  @Test(expected = DescriptorParseException.class)
-  public void testUnrecognizedLineFail()
-      throws DescriptorParseException {
+  @Test
+  public void testUnrecognizedLineFail() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line 'unrecognized-line 1' in "
+        + "extra-info descriptor.");
     String unrecognizedLine = "unrecognized-line 1";
     DescriptorBuilder.createWithUnrecognizedLine(unrecognizedLine, true);
   }
@@ -2036,9 +2213,12 @@ public class ExtraInfoDescriptorImplTest {
         descriptor.getRouterSignatureEd25519());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519IdentityMasterKeyMismatch()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Mismatch between identity-ed25519 and master-key-ed25519.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
         "master-key-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
         ROUTER_SIG_ED25519_LINE);
@@ -2051,17 +2231,23 @@ public class ExtraInfoDescriptorImplTest {
         MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519IdentityDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'identity-ed25519' is contained 2 times,"
+        + " but must be contained at most once.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES + "\n"
         + IDENTITY_ED25519_LINES, MASTER_KEY_ED25519_LINE,
         ROUTER_SIG_ED25519_LINE);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519IdentityEmptyCrypto()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Invalid length of identity-ed25519 (in bytes): 0");
     DescriptorBuilder.createWithEd25519Lines("identity-ed25519\n"
         + "-----BEGIN ED25519 CERT-----\n-----END ED25519 CERT-----",
         MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
@@ -2078,9 +2264,12 @@ public class ExtraInfoDescriptorImplTest {
         descriptor.getMasterKeyEd25519());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519MasterKeyDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'master-key-ed25519' is contained 2 "
+        + "times, but must be contained at most once.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
         MASTER_KEY_ED25519_LINE + "\n" + MASTER_KEY_ED25519_LINE,
         ROUTER_SIG_ED25519_LINE);
@@ -2093,17 +2282,23 @@ public class ExtraInfoDescriptorImplTest {
         MASTER_KEY_ED25519_LINE, null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519RouterSigDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'router-sig-ed25519' is contained "
+        + "2 times, but must be contained at most once.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
         MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE + "\n"
         + ROUTER_SIG_ED25519_LINE);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519FollowedbyUnrecognizedLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'unrecognized-line 1' in extra-info descriptor.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
         MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE
         + "\nunrecognized-line 1");
diff --git a/src/test/java/org/torproject/descriptor/impl/MicrodescriptorImplTest.java b/src/test/java/org/torproject/descriptor/impl/MicrodescriptorImplTest.java
index 140974c..7167de9 100644
--- a/src/test/java/org/torproject/descriptor/impl/MicrodescriptorImplTest.java
+++ b/src/test/java/org/torproject/descriptor/impl/MicrodescriptorImplTest.java
@@ -8,10 +8,15 @@ import static org.junit.Assert.assertEquals;
 import org.torproject.descriptor.DescriptorParseException;
 import org.torproject.descriptor.Microdescriptor;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 public class MicrodescriptorImplTest {
 
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   /* Helper class to build a microdescriptor based on default data and
    * modifications requested by test methods. */
   private static class DescriptorBuilder {
@@ -73,25 +78,39 @@ public class MicrodescriptorImplTest {
         micro.getDigestSha256Base64());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testIdRsa1024TooShort() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'AAAA' in line 'id rsa1024 AAAA' is not a "
+        + "valid base64-encoded 20-byte value.");
     DescriptorBuilder.createWithIdLine("id rsa1024 AAAA");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testIdRsa1024TooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+        + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' in line 'id ed25519 "
+        + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+        + "AAAAAAAAAAAAAAAAAAA' is not a valid base64-encoded 32-byte value.");
     DescriptorBuilder.createWithIdLine("id ed25519 AAAAAAAAAAAAAAAAAAAAAA"
         + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testIdRsa512() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal line 'id rsa512 bvegfGxp8k7T9QFpjPTrPaJTa/8'.");
     DescriptorBuilder.createWithIdLine("id rsa512 "
         + "bvegfGxp8k7T9QFpjPTrPaJTa/8");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testIdEd25519Duplicate() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'id' is contained 2 times, but must be"
+        + " contained at most once.");
     DescriptorBuilder.createWithIdLine(
         "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8\n"
         + "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8");
diff --git a/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java
index d6bf4cf..dcd3663 100644
--- a/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java
+++ b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java
@@ -14,7 +14,9 @@ import org.torproject.descriptor.DirectorySignature;
 import org.torproject.descriptor.NetworkStatusEntry;
 import org.torproject.descriptor.RelayNetworkStatusConsensus;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -29,6 +31,9 @@ import java.util.TreeSet;
  * a parsed consensus is fully compatible to dir-spec.txt. */
 public class RelayNetworkStatusConsensusImplTest {
 
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   /* Helper class to build a directory source based on default data and
    * modifications requested by test methods. */
   private static class DirSourceBuilder {
@@ -392,22 +397,29 @@ public class RelayNetworkStatusConsensusImplTest {
     assertTrue(consensus.getUnrecognizedLines().isEmpty());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionNoLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'network-status-version' must be "
+        + "contained in the first line.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionNewLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Blank lines are not allowed.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(
         "network-status-version 3\n");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionNewLineSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal keyword in line ' '.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(
         "network-status-version 3\n ");
   }
@@ -419,72 +431,104 @@ public class RelayNetworkStatusConsensusImplTest {
         "@consensus\nnetwork-status-version 3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionPrefixLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'directory-footer' in consensus.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(
         "directory-footer\nnetwork-status-version 3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionPrefixLinePoundChar()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line '#consensus' in consensus.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(
         "#consensus\nnetwork-status-version 3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionNoSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal network status version number in line "
+        + "'network-status-version'.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(
         "network-status-version");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionOneSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal network status version number in line "
+        + "'network-status-version '.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(
         "network-status-version ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersion42()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal network status version number in line "
+        + "'network-status-version 42'.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(
         "network-status-version 42");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionFourtyTwo()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal network status version number in line "
+        + "'network-status-version FourtyTwo'.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(
         "network-status-version FourtyTwo");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'vote-status' is contained 0 times, "
+        + "but must be contained exactly once.");
     ConsensusBuilder.createWithVoteStatusLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionSpaceBefore()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal keyword in line ' network-status-version 3'.");
     ConsensusBuilder.createWithNetworkStatusVersionLine(
         " network-status-version 3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusSpaceBefore() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal keyword in line ' vote-status consensus'.");
     ConsensusBuilder.createWithVoteStatusLine(" vote-status consensus");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Line 'vote-status' indicates that this is not a consensus.");
     ConsensusBuilder.createWithVoteStatusLine("vote-status");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusOneSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Line 'vote-status ' indicates that this is not a consensus.");
     ConsensusBuilder.createWithVoteStatusLine("vote-status ");
   }
 
@@ -494,135 +538,201 @@ public class RelayNetworkStatusConsensusImplTest {
     ConsensusBuilder.createWithVoteStatusLine("vote-status consensus ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusVote() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Line 'vote-status vote' indicates that this is not a consensus.");
     ConsensusBuilder.createWithVoteStatusLine("vote-status vote");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusTheMagicVoteStatus()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'vote-status TheMagicVoteStatus' indicates "
+        + "that this is not a consensus.");
     ConsensusBuilder.createWithVoteStatusLine(
         "vote-status TheMagicVoteStatus");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodNoLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'consensus-method' is contained 0 "
+        + "times, but must be contained exactly once.");
     ConsensusBuilder.createWithConsensusMethodLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodNoSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'consensus-method' in consensus.");
     ConsensusBuilder.createWithConsensusMethodLine("consensus-method");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodOneSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'consensus-method ' in consensus.");
     ConsensusBuilder.createWithConsensusMethodLine("consensus-method ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodEleven()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal consensus method number in line 'consensus-method eleven'.");
     ConsensusBuilder.createWithConsensusMethodLine(
         "consensus-method eleven");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodMinusOne()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal consensus method number in line "
+        + "'consensus-method -1'.");
     ConsensusBuilder.createWithConsensusMethodLine("consensus-method -1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodNinePeriod()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal consensus method number in line "
+        + "'consensus-method 99999999999999999999999999999999999999999999"
+        + "9999999999999999'.");
     ConsensusBuilder.createWithConsensusMethodLine("consensus-method "
         + "999999999999999999999999999999999999999999999999999999999999");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodTwoLines()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'consensus-method' is contained 2 times,"
+        + " but must be contained exactly once.");
     ConsensusBuilder.createWithConsensusMethodLine(
         "consensus-method 1\nconsensus-method 1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'valid-after' is contained 0 times, "
+        + "but must be contained exactly once.");
     ConsensusBuilder.createWithValidAfterLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'valid-after' does not contain a "
+        + "timestamp at the expected position.");
     ConsensusBuilder.createWithValidAfterLine("valid-after");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterOneSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'valid-after ' does not contain a timestamp"
+        + " at the expected position.");
     ConsensusBuilder.createWithValidAfterLine("valid-after ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterLongAgo() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal timestamp format in line 'valid-after long ago'.");
     ConsensusBuilder.createWithValidAfterLine("valid-after long ago");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterFeb30() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'valid-after 2011-02-30 09:00:00'.");
     ConsensusBuilder.createWithValidAfterLine(
         "valid-after 2011-02-30 09:00:00");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFreshUntilNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'fresh-until' is contained 0 times, "
+        + "but must be contained exactly once.");
     ConsensusBuilder.createWithFreshUntilLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFreshUntilAroundTen() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'fresh-until 2011-11-30 around ten'.");
     ConsensusBuilder.createWithFreshUntilLine(
         "fresh-until 2011-11-30 around ten");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidUntilTomorrowMorning()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal timestamp format in line 'valid-until tomorrow morning'.");
     ConsensusBuilder.createWithValidUntilLine(
         "valid-until tomorrow morning");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'voting-delay' is contained 0 times, "
+        + "but must be contained exactly once.");
     ConsensusBuilder.createWithVotingDelayLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Wrong number of values in line 'voting-delay'.");
     ConsensusBuilder.createWithVotingDelayLine("voting-delay");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayOneSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Wrong number of values in line 'voting-delay '.");
     ConsensusBuilder.createWithVotingDelayLine("voting-delay ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayTriple() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Wrong number of values in line 'voting-delay 300 300 300'.");
     ConsensusBuilder.createWithVotingDelayLine(
         "voting-delay 300 300 300");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelaySingle() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Wrong number of values in line 'voting-delay 300'.");
     ConsensusBuilder.createWithVotingDelayLine("voting-delay 300");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayOneTwo() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal values in line 'voting-delay one two'.");
     ConsensusBuilder.createWithVotingDelayLine("voting-delay one two");
   }
 
@@ -671,14 +781,19 @@ public class RelayNetworkStatusConsensusImplTest {
     assertTrue(consensus.getRecommendedClientVersions().isEmpty());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testClientVersionsComma() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal versions line 'client-versions ,'.");
     ConsensusBuilder.createWithClientVersionsLine("client-versions ,");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testClientVersionsCommaVersion()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal versions line 'client-versions ,0.2.2.34'.");
     ConsensusBuilder.createWithClientVersionsLine(
         "client-versions ,0.2.2.34");
   }
@@ -724,9 +839,12 @@ public class RelayNetworkStatusConsensusImplTest {
         consensus.getRequiredRelayProtocols().get("Cons"));
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testRequiredRelayProtocolsTwice()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'required-relay-protocols' is contained "
+        + "2 times, but must be contained at most once.");
     ConsensusBuilder.createWithRequiredRelayProtocolsLine(
         "required-relay-protocols Cons=1\nrequired-relay-protocols Cons=1");
   }
@@ -761,24 +879,34 @@ public class RelayNetworkStatusConsensusImplTest {
     }
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPackageIncomplete() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Wrong number of values in line 'package shouldbesecond 0 http'.");
     String packageLine = "package shouldbesecond 0 http";
     ConsensusBuilder.createWithPackageLines(packageLine);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testKnownFlagsNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'known-flags' is contained 0 times, "
+        + "but must be contained exactly once.");
     ConsensusBuilder.createWithKnownFlagsLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testKnownFlagsNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("No known flags in line 'known-flags'.");
     ConsensusBuilder.createWithKnownFlagsLine("known-flags");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testKnownFlagsOneSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("No known flags in line 'known-flags '.");
     ConsensusBuilder.createWithKnownFlagsLine("known-flags ");
   }
 
@@ -813,14 +941,20 @@ public class RelayNetworkStatusConsensusImplTest {
     assertTrue(consensus.getConsensusParams().isEmpty());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testParamsNoEqualSign() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'params key-value' contains an illegal "
+        + "value in list element 'key-value'.");
     ConsensusBuilder.createWithParamsLine("params key-value");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testParamsOneTooLargeNegative()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'params min=-2147483649' contains an "
+        + "illegal value in list element 'min=-2147483649'.");
     ConsensusBuilder.createWithParamsLine("params min=-2147483649");
   }
 
@@ -844,22 +978,31 @@ public class RelayNetworkStatusConsensusImplTest {
         (int) consensus.getConsensusParams().get("max"));
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testParamsOneTooLargePositive()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'params max=2147483648' contains an illegal"
+        + " value in list element 'max=2147483648'.");
     ConsensusBuilder.createWithParamsLine("params max=2147483648");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testSharedRandPreviousNumRevealsOnly()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal line 'shared-rand-previous-value 8' in vote.");
     ConsensusBuilder.createWithSharedRandPreviousValueLine(
         "shared-rand-previous-value 8");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testSharedRandPreviousExtraArg()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'shared-rand-current-value 8 "
+        + "D88plxd8YeLfCIVAR9gjiFlWB1WqpC53kWr350o1pzw= -1.0' in vote.");
     ConsensusBuilder.createWithSharedRandCurrentValueLine(
         "shared-rand-current-value 8 "
             + "D88plxd8YeLfCIVAR9gjiFlWB1WqpC53kWr350o1pzw= -1.0");
@@ -880,62 +1023,101 @@ public class RelayNetworkStatusConsensusImplTest {
         "81349FC1F2DBA2C2C11B45CB9706637D480AB913").isLegacy());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceNicknameTooLong()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line 'dir-source "
+        + "gabelmooisfinebutthisistoolong ED03BB616EB2F60BEC80151114BB25CEF5"
+        + "15B226 212.112.245.170 212.112.245.170 80 443'.");
     DirSourceBuilder.createWithNickname("gabelmooisfinebutthisistoolong");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceIdentityTooShort()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'dir-source gabelmoo "
+        + "ED03BB616EB2F60BEC8015111 212.112.245.170 212.112.245.170 80 443'.");
     DirSourceBuilder.createWithIdentity("ED03BB616EB2F60BEC8015111");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceIdentityTooLong()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'dir-source gabelmoo "
+        + "ED03BB616EB2F60BEC80151114BB25CEF515B226ED03BB616EB2F60BEC8 "
+        + "212.112.245.170 212.112.245.170 80 443'.");
     DirSourceBuilder.createWithIdentity("ED03BB616EB2F60BEC8015111"
         + "4BB25CEF515B226ED03BB616EB2F60BEC8");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceHostnameMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid line 'dir-source gabelmoo "
+        + "ED03BB616EB2F60BEC80151114BB25CEF515B226  212.112.245.170 80 443'.");
     DirSourceBuilder.createWithHostName("");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceAddress24() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'212.112.245' in line 'dir-source "
+        + "gabelmoo ED03BB616EB2F60BEC80151114BB25CEF515B226 212.112.245.170 "
+        + "212.112.245 80 443' is not a valid IPv4 address.");
     DirSourceBuilder.createWithAddress("212.112.245");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceAddress40() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "'212.112.245.170.123' in line 'dir-source gabelmoo "
+        + "ED03BB616EB2F60BEC80151114BB25CEF515B226 212.112.245.170 212.112.245"
+        + ".170.123 80 443' is not a valid IPv4 address.");
     DirSourceBuilder.createWithAddress("212.112.245.170.123");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceDirPortMinusOne()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'-1' in line 'dir-source gabelmoo "
+        + "ED03BB616EB2F60BEC80151114BB25CEF515B226 212.112.245.170 "
+        + "212.112.245.170 -1 443' is not a valid port number.");
     DirSourceBuilder.createWithDirPort("-1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceDirPort66666()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'66666' in line 'dir-source gabelmoo "
+        + "ED03BB616EB2F60BEC80151114BB25CEF515B226 212.112.245.170 "
+        + "212.112.245.170 66666 443' is not a valid port number.");
     DirSourceBuilder.createWithDirPort("66666");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceDirPortOnions()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'onions' in line 'dir-source gabelmoo "
+        + "ED03BB616EB2F60BEC80151114BB25CEF515B226 212.112.245.170 "
+        + "212.112.245.170 onions 443' is not a valid port number.");
     DirSourceBuilder.createWithDirPort("onions");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceOrPortOnions()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'onions' in line 'dir-source gabelmoo "
+        + "ED03BB616EB2F60BEC80151114BB25CEF515B226 212.112.245.170 "
+        + "212.112.245.170 80 onions' is not a valid port number.");
     DirSourceBuilder.createWithOrPort("onions");
   }
 
@@ -966,113 +1148,193 @@ public class RelayNetworkStatusConsensusImplTest {
         "ED03BB616EB2F60BEC80151114BB25CEF515B226").getContactLine());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceVoteDigestNoLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "dir-source does not contain a 'vote-digest' line.");
     DirSourceBuilder.createWithVoteDigestLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceVoteDigestLineNoSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid line 'vote-digest'.");
     DirSourceBuilder.createWithVoteDigestLine("vote-digest");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceVoteDigestLineOneSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid line 'vote-digest '.");
     DirSourceBuilder.createWithVoteDigestLine("vote-digest ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameNotAllowedChars()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line 'r notAll()wed "
+        + "ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 "
+        + "21:34:27 50.63.8.215 9023 0'.");
     StatusEntryBuilder.createWithNickname("notAll()wed");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameTooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line "
+        + "'r 1234567890123456789tooLong ADQ6gCT3DiFHKPDFr3rODBUI8HM "
+        + "Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 21:34:27 50.63.8.215 "
+        + "9023 0'.");
     StatusEntryBuilder.createWithNickname("1234567890123456789tooLong");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintTooShort() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'TooShort' in line 'r right2privassy3 TooShort "
+        + "Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 21:34:27 50.63.8.215 9023 0' "
+        + "is not a valid base64-encoded 20-byte value.");
     StatusEntryBuilder.createWithFingerprintBase64("TooShort");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintEndsWithEqualSign()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'ADQ6gCT3DiFHKPDFr3rODBUI8H=' in line 'r "
+        + "right2privassy3 ADQ6gCT3DiFHKPDFr3rODBUI8H= Yiti+nayuT2Efe2X1+M4"
+        + "nslwVuU 2011-11-29 21:34:27 50.63.8.215 9023 0' is not a valid "
+        + "base64-encoded 20-byte value.");
     StatusEntryBuilder.createWithFingerprintBase64(
         "ADQ6gCT3DiFHKPDFr3rODBUI8H=");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintTooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'ADQ6gCT3DiFHKPDFr3rODBUI8HMAAAA' in line "
+        + "'r right2privassy3 ADQ6gCT3DiFHKPDFr3rODBUI8HMAAAA Yiti+nayuT2Efe2X1"
+        + "+M4nslwVuU 2011-11-29 21:34:27 50.63.8.215 9023 0' is not a valid "
+        + "base64-encoded 20-byte value.");
     StatusEntryBuilder.createWithFingerprintBase64(
         "ADQ6gCT3DiFHKPDFr3rODBUI8HMAAAA");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDescriptorTooShort() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'TooShort' in line 'r right2privassy3 "
+        + "ADQ6gCT3DiFHKPDFr3rODBUI8HM TooShort 2011-11-29 21:34:27 50.63.8.215"
+        + " 9023 0' is not a valid base64-encoded 20-byte value.");
     StatusEntryBuilder.createWithDescriptorBase64("TooShort");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDescriptorEndsWithEqualSign()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'ADQ6gCT3DiFHKPDFr3rODBUI8H=' in line "
+        + "'r right2privassy3 ADQ6gCT3DiFHKPDFr3rODBUI8HM "
+        + "ADQ6gCT3DiFHKPDFr3rODBUI8H= 2011-11-29 21:34:27 50.63.8.215 9023 0' "
+        + "is not a valid base64-encoded 20-byte value.");
     StatusEntryBuilder.createWithDescriptorBase64(
         "ADQ6gCT3DiFHKPDFr3rODBUI8H=");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDescriptorTooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'Yiti+nayuT2Efe2X1+M4nslwVuUAAAA' in line "
+        + "'r right2privassy3 ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4n"
+        + "slwVuUAAAA 2011-11-29 21:34:27 50.63.8.215 9023 0' is not a valid "
+        + "base64-encoded 20-byte value.");
     StatusEntryBuilder.createWithDescriptorBase64(
         "Yiti+nayuT2Efe2X1+M4nslwVuUAAAA");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPublished1960() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line 'r "
+        + "right2privassy3 ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4ns"
+        + "lwVuU 1960-11-29 21:34:27 50.63.8.215 9023 0'.");
     StatusEntryBuilder.createWithPublishedString("1960-11-29 21:34:27");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPublished9999() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line 'r "
+        + "right2privassy3 ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+"
+        + "M4nslwVuU 9999-11-29 21:34:27 50.63.8.215 9023 0'.");
     StatusEntryBuilder.createWithPublishedString("9999-11-29 21:34:27");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAddress256() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'256.63.8.215' in line 'r right2privassy3 "
+        + "ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 "
+        + "21:34:27 256.63.8.215 9023 0' is not a valid IPv4 address.");
     StatusEntryBuilder.createWithAddress("256.63.8.215");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAddress24() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'50.63.8/24' in line 'r right2privassy3 "
+        + "ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 "
+        + "21:34:27 50.63.8/24 9023 0' is not a valid IPv4 address.");
     StatusEntryBuilder.createWithAddress("50.63.8/24");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAddressV6() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'::1' in line 'r right2privassy3 "
+        + "ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 "
+        + "21:34:27 ::1 9023 0' is not a valid IPv4 address.");
     StatusEntryBuilder.createWithAddress("::1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testOrPort66666() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'66666' in line 'r right2privassy3 "
+        + "ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 "
+        + "21:34:27 50.63.8.215 66666 0' is not a valid port number.");
     StatusEntryBuilder.createWithOrPort("66666");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testOrPortEighty() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'eighty' in line 'r right2privassy3 "
+        + "ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 "
+        + "21:34:27 50.63.8.215 eighty 0' is not a valid port number.");
     StatusEntryBuilder.createWithOrPort("eighty");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirPortMinusOne() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'-1' in line 'r right2privassy3 ADQ6gCT3DiFHKP"
+        + "DFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 21:34:27 "
+        + "50.63.8.215 9023 -1' is not a valid port number.");
     StatusEntryBuilder.createWithDirPort("-1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirPortZero() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'zero' in line 'r right2privassy3 "
+        + "ADQ6gCT3DiFHKPDFr3rODBUI8HM Yiti+nayuT2Efe2X1+M4nslwVuU 2011-11-29 "
+        + "21:34:27 50.63.8.215 9023 zero' is not a valid port number.");
     StatusEntryBuilder.createWithDirPort("zero");
   }
 
@@ -1092,8 +1354,10 @@ public class RelayNetworkStatusConsensusImplTest {
         "00343A8024F70E214728F0C5AF7ACE0C1508F073").getFlags().isEmpty());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testTwoSLines() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Duplicate 's' line in status entry.");
     StatusEntryBuilder sb = new StatusEntryBuilder();
     sb.sLine = sb.sLine + "\n" + sb.sLine;
     ConsensusBuilder cb = new ConsensusBuilder();
@@ -1101,8 +1365,10 @@ public class RelayNetworkStatusConsensusImplTest {
     cb.buildConsensus(true);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testTwoPrLines() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Duplicate 'pr' line in status entry.");
     StatusEntryBuilder sb = new StatusEntryBuilder();
     sb.prLine = sb.prLine + "\n" + sb.prLine;
     ConsensusBuilder cb = new ConsensusBuilder();
@@ -1110,13 +1376,17 @@ public class RelayNetworkStatusConsensusImplTest {
     cb.buildConsensus(true);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWLineNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'w'.");
     StatusEntryBuilder.createWithWLine("w");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWLineOneSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'w '.");
     StatusEntryBuilder.createWithWLine("w ");
   }
 
@@ -1125,8 +1395,10 @@ public class RelayNetworkStatusConsensusImplTest {
     StatusEntryBuilder.createWithWLine("w Warp=7");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testTwoWLines() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Duplicate 'w' line in status entry.");
     StatusEntryBuilder sb = new StatusEntryBuilder();
     sb.wLine = sb.wLine + "\n" + sb.wLine;
     ConsensusBuilder cb = new ConsensusBuilder();
@@ -1157,28 +1429,38 @@ public class RelayNetworkStatusConsensusImplTest {
     }
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPLineNoPolicy() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'p 80'.");
     StatusEntryBuilder.createWithPLine("p 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPLineNoPorts() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'p accept'.");
     StatusEntryBuilder.createWithPLine("p accept");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPLineNoPolicyNoPorts() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'p '.");
     StatusEntryBuilder.createWithPLine("p ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPLineProject() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'p project 80'.");
     StatusEntryBuilder.createWithPLine("p project 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testTwoPLines() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Duplicate 'p' line in status entry.");
     StatusEntryBuilder sb = new StatusEntryBuilder();
     sb.pLine = sb.pLine + "\n" + sb.pLine;
     ConsensusBuilder cb = new ConsensusBuilder();
@@ -1195,9 +1477,14 @@ public class RelayNetworkStatusConsensusImplTest {
         "00795A6E8D91C270FC23B30F388A495553E01894"));
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirectoryFooterNoLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line 'bandwidth-weights Wbd=285 "
+        + "Wbe=0 Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=1021 Wee=10000 "
+        + "Weg=1021 Wem=10000 Wgb=10000 Wgd=8694 Wgg=10000 Wgm=10000 Wmb=10000 "
+        + "Wmd=285 Wme=0 Wmg=0 Wmm=10000' in status entry.");
     /* This breaks, because a bandwidth-weights line without a preceding
      * directory-footer line is not allowed. */
     ConsensusBuilder.createWithDirectoryFooterLine(null);
@@ -1245,16 +1532,22 @@ public class RelayNetworkStatusConsensusImplTest {
     assertNotNull(consensus.getBandwidthWeights());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBandwidthWeightsLineNoEqualSign()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'bandwidth-weights Wbd-285' contains an "
+        + "illegal value in list element 'Wbd-285'.");
     ConsensusBuilder.createWithBandwidthWeightsLine(
         "bandwidth-weights Wbd-285");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirectorySignatureIdentityTooShort()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'directory-signature "
+        + "ED03BB616EB2F60 845CF1D0B370CA443A8579D18E7987E7E532F639'.");
     DirectorySignatureBuilder.createWithIdentity("ED03BB616EB2F60");
   }
 
@@ -1277,9 +1570,12 @@ public class RelayNetworkStatusConsensusImplTest {
     DirectorySignatureBuilder.createWithSigningKey("845CF1D0B370CA");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirectorySignatureSigningKeyTooShortOddNumber()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'directory-signature "
+        + "ED03BB616EB2F60BEC80151114BB25CEF515B226 845'.");
     /* We don't accept this hex string, because it contains an odd number
      * of hex characters. */
     DirectorySignatureBuilder.createWithSigningKey("845");
@@ -1294,8 +1590,11 @@ public class RelayNetworkStatusConsensusImplTest {
         "845CF1D0B370CA443A8579D18E7987E7E532F639845CF1D0B370CA443A");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNonAsciiByte20() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line 'network-status-versi�n 3' in "
+        + "consensus.");
     ConsensusBuilder cb = new ConsensusBuilder();
     byte[] consensusBytes = cb.buildConsensusBytes();
     consensusBytes[20] = (byte) 200;
@@ -1303,9 +1602,12 @@ public class RelayNetworkStatusConsensusImplTest {
         new int[] { 0, consensusBytes.length }, true);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNonAsciiByteMinusOne()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line '�network-status-version 3' in consensus.");
     ConsensusBuilder cb = new ConsensusBuilder();
     cb.networkStatusVersionLine = "Xnetwork-status-version 3";
     byte[] consensusBytes = cb.buildConsensusBytes();
@@ -1314,9 +1616,12 @@ public class RelayNetworkStatusConsensusImplTest {
         new int[] { 0, consensusBytes.length }, true);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUnrecognizedHeaderLineFail()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'unrecognized-line 1' in consensus.");
     String unrecognizedLine = "unrecognized-line 1";
     ConsensusBuilder.createWithUnrecognizedHeaderLine(unrecognizedLine,
         true);
@@ -1333,9 +1638,12 @@ public class RelayNetworkStatusConsensusImplTest {
     assertEquals(unrecognizedLines, consensus.getUnrecognizedLines());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUnrecognizedDirSourceLineFail()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'unrecognized-line 1' in dir-source entry.");
     String unrecognizedLine = "unrecognized-line 1";
     ConsensusBuilder.createWithUnrecognizedDirSourceLine(unrecognizedLine,
         true);
@@ -1352,9 +1660,12 @@ public class RelayNetworkStatusConsensusImplTest {
     assertEquals(unrecognizedLines, consensus.getUnrecognizedLines());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUnrecognizedStatusEntryLineFail()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line 'unrecognized-line 1' in "
+        + "status entry.");
     String unrecognizedLine = "unrecognized-line 1";
     ConsensusBuilder.createWithUnrecognizedStatusEntryLine(
         unrecognizedLine, true);
@@ -1371,9 +1682,12 @@ public class RelayNetworkStatusConsensusImplTest {
     assertEquals(unrecognizedLines, consensus.getUnrecognizedLines());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUnrecognizedDirectoryFooterLineFail()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'unrecognized-line 1' in consensus.");
     String unrecognizedLine = "unrecognized-line 1";
     ConsensusBuilder.createWithUnrecognizedFooterLine(unrecognizedLine,
         true);
@@ -1390,9 +1704,12 @@ public class RelayNetworkStatusConsensusImplTest {
     assertEquals(unrecognizedLines, consensus.getUnrecognizedLines());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUnrecognizedDirectorySignatureLineFail()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'unrecognized-line 1' in dir-source entry.");
     String unrecognizedLine = "unrecognized-line 1";
     ConsensusBuilder.createWithUnrecognizedDirectorySignatureLine(
         unrecognizedLine, true);
diff --git a/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusImplTest.java b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusImplTest.java
index d0ff7fb..efca978 100644
--- a/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusImplTest.java
+++ b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusImplTest.java
@@ -7,10 +7,15 @@ import static org.junit.Assert.assertEquals;
 
 import org.torproject.descriptor.DescriptorParseException;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 public class RelayNetworkStatusImplTest {
 
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   private static final String validAnnotation = "@type network-status-2 1.0\n";
 
   private static final String validHeader = "network-status-version 2\n"
@@ -36,8 +41,10 @@ public class RelayNetworkStatusImplTest {
   private static final String validStatus =
       validAnnotation + validHeader + validFooter;
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testParseBrokenHeader() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line 'xyx' in v2 network status.");
     String invalidHeader = "network-status-version 2\nxyx\nabc";
     byte[] statusBytes = (validAnnotation + invalidHeader + validFooter)
         .getBytes();
diff --git a/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
index aa7ff4a..1997c46 100644
--- a/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
+++ b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
@@ -11,7 +11,9 @@ import org.torproject.descriptor.DescriptorParseException;
 import org.torproject.descriptor.DirectorySignature;
 import org.torproject.descriptor.RelayNetworkStatusVote;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -26,6 +28,9 @@ import java.util.TreeSet;
  * in the directory header. */
 public class RelayNetworkStatusVoteImplTest {
 
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   /* Helper class to build a vote based on default data and modifications
    * requested by test methods. */
   private static class VoteBuilder {
@@ -745,22 +750,29 @@ public class RelayNetworkStatusVoteImplTest {
     assertTrue(vote.getUnrecognizedLines().isEmpty());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionNoLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'network-status-version' must be "
+        + "contained in the first line.");
     VoteBuilder.createWithNetworkStatusVersionLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionNewLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Blank lines are not allowed.");
     VoteBuilder.createWithNetworkStatusVersionLine(
         "network-status-version 3\n");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionNewLineSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal keyword in line ' '.");
     VoteBuilder.createWithNetworkStatusVersionLine(
         "network-status-version 3\n ");
   }
@@ -772,72 +784,102 @@ public class RelayNetworkStatusVoteImplTest {
         "@vote\nnetwork-status-version 3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionPrefixLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line 'directory-footer' in vote.");
     VoteBuilder.createWithNetworkStatusVersionLine(
         "directory-footer\nnetwork-status-version 3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionPrefixLinePoundChar()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line '#vote' in vote.");
     VoteBuilder.createWithNetworkStatusVersionLine(
         "#vote\nnetwork-status-version 3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionNoSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal network status version number "
+        + "in line 'network-status-version'.");
     VoteBuilder.createWithNetworkStatusVersionLine(
         "network-status-version");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionOneSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal network status version number in "
+        + "line 'network-status-version '.");
     VoteBuilder.createWithNetworkStatusVersionLine(
         "network-status-version ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersion42()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal network status version number in line "
+        + "'network-status-version 42'.");
     VoteBuilder.createWithNetworkStatusVersionLine(
         "network-status-version 42");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionFourtyTwo()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal network status version number in line "
+        + "'network-status-version FourtyTwo'.");
     VoteBuilder.createWithNetworkStatusVersionLine(
         "network-status-version FourtyTwo");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'vote-status' is contained 0 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithVoteStatusLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNetworkStatusVersionSpaceBefore()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal keyword in line ' network-status-version 3'.");
     VoteBuilder.createWithNetworkStatusVersionLine(
         " network-status-version 3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusSpaceBefore() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal keyword in line ' vote-status vote'.");
     VoteBuilder.createWithVoteStatusLine(" vote-status vote");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown
+        .expectMessage("Line 'vote-status' indicates that this is not a vote.");
     VoteBuilder.createWithVoteStatusLine("vote-status");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusOneSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Line 'vote-status ' indicates that this is not a vote.");
     VoteBuilder.createWithVoteStatusLine("vote-status ");
   }
 
@@ -847,14 +889,20 @@ public class RelayNetworkStatusVoteImplTest {
     VoteBuilder.createWithVoteStatusLine("vote-status vote ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusConsensus() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'vote-status consensus' indicates that "
+        + "this is not a vote.");
     VoteBuilder.createWithVoteStatusLine("vote-status consensus");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVoteStatusTheMagicVoteStatus()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'vote-status TheMagicVoteStatus' "
+        + "indicates that this is not a vote.");
     VoteBuilder.createWithVoteStatusLine(
         "vote-status TheMagicVoteStatus");
   }
@@ -867,140 +915,208 @@ public class RelayNetworkStatusVoteImplTest {
     assertNull(vote.getConsensusMethods());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodNoSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'consensus-methods' in vote.");
     VoteBuilder.createWithConsensusMethodsLine("consensus-methods");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodOneSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'consensus-methods ' in vote.");
     VoteBuilder.createWithConsensusMethodsLine("consensus-methods ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodEleven()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal consensus method number in line "
+        + "'consensus-methods eleven'.");
     VoteBuilder.createWithConsensusMethodsLine(
         "consensus-methods eleven");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodMinusOne()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal consensus method number in line "
+        + "'consensus-methods -1'.");
     VoteBuilder.createWithConsensusMethodsLine("consensus-methods -1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodNinePeriod()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal consensus method number in line "
+        + "'consensus-methods 99999999999999999999999999999999999999999"
+        + "9999999999999999999'.");
     VoteBuilder.createWithConsensusMethodsLine("consensus-methods "
         + "999999999999999999999999999999999999999999999999999999999999");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testConsensusMethodTwoLines()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'consensus-method 1' in vote.");
     VoteBuilder.createWithConsensusMethodsLine(
         "consensus-method 1\nconsensus-method 1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPublishedNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'published' is contained 0 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithPublishedLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'valid-after' is contained 0 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithValidAfterLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'valid-after' does not contain a "
+        + "timestamp at the expected position.");
     VoteBuilder.createWithValidAfterLine("valid-after");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterOneSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'valid-after ' does not contain a "
+        + "timestamp at the expected position.");
     VoteBuilder.createWithValidAfterLine("valid-after ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterLongAgo() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line 'valid-after "
+        + "long ago'.");
     VoteBuilder.createWithValidAfterLine("valid-after long ago");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidAfterFeb30() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'valid-after 2011-02-30 09:00:00'.");
     VoteBuilder.createWithValidAfterLine(
         "valid-after 2011-02-30 09:00:00");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFreshUntilNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'fresh-until' is contained 0 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithFreshUntilLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFreshUntilAroundTen() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'fresh-until 2011-11-30 around ten'.");
     VoteBuilder.createWithFreshUntilLine(
         "fresh-until 2011-11-30 around ten");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testValidUntilTomorrowMorning()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line 'valid-until "
+        + "tomorrow morning'.");
     VoteBuilder.createWithValidUntilLine(
         "valid-until tomorrow morning");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'voting-delay' is contained 0 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithVotingDelayLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Wrong number of values in line 'voting-delay'.");
     VoteBuilder.createWithVotingDelayLine("voting-delay");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayOneSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown
+        .expectMessage("Wrong number of values in line 'voting-delay '.");
     VoteBuilder.createWithVotingDelayLine("voting-delay ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayTriple() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Wrong number of values in line 'voting-delay 300 300 300'.");
     VoteBuilder.createWithVotingDelayLine(
         "voting-delay 300 300 300");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelaySingle() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Wrong number of values in line 'voting-delay 300'.");
     VoteBuilder.createWithVotingDelayLine("voting-delay 300");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testVotingDelayOneTwo() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal values in line 'voting-delay one two'.");
     VoteBuilder.createWithVotingDelayLine("voting-delay one two");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testClientVersionsComma() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal versions line 'client-versions ,'.");
     VoteBuilder.createWithClientVersionsLine("client-versions ,");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testClientVersionsCommaVersion()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal versions line 'client-versions ,0.2.2.34'.");
     VoteBuilder.createWithClientVersionsLine(
         "client-versions ,0.2.2.34");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testRecommendedClientProtocols21()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Invalid line 'recommended-client-protocols Cons=2-1'.");
     VoteBuilder.createWithRecommendedClientProtocolsLine(
         "recommended-client-protocols Cons=2-1");
   }
@@ -1015,16 +1131,22 @@ public class RelayNetworkStatusVoteImplTest {
         vote.getRecommendedRelayProtocols().get("Cons"));
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testRequiredClientProtocols1Max()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Invalid line 'recommended-client-protocols Cons=1-4294967296'.");
     VoteBuilder.createWithRequiredClientProtocolsLine(
         "recommended-client-protocols Cons=1-4294967296");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testRequiredRelayProtocolsMinus1()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown
+        .expectMessage("Invalid line 'recommended-client-protocols Cons=-1'.");
     VoteBuilder.createWithRequiredRelayProtocolsLine(
         "recommended-client-protocols Cons=-1");
   }
@@ -1059,24 +1181,34 @@ public class RelayNetworkStatusVoteImplTest {
     }
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPackageIncomplete() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Wrong number of values in line "
+        + "'package shouldbesecond 0 http'.");
     String packageLine = "package shouldbesecond 0 http";
     ConsensusBuilder.createWithPackageLines(packageLine);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testKnownFlagsNoLine() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'known-flags' is contained 0 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithKnownFlagsLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testKnownFlagsNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("No known flags in line 'known-flags'.");
     VoteBuilder.createWithKnownFlagsLine("known-flags");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testKnownFlagsOneSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("No known flags in line 'known-flags '.");
     VoteBuilder.createWithKnownFlagsLine("known-flags ");
   }
 
@@ -1126,45 +1258,63 @@ public class RelayNetworkStatusVoteImplTest {
     assertEquals(0, vote.getEnoughMtbfInfo());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFlagThresholdsNoSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("No flag thresholds in line 'flag-thresholds'.");
     VoteBuilder.createWithFlagThresholdsLine("flag-thresholds");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFlagThresholdsOneSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("No flag thresholds in line 'flag-thresholds '.");
     VoteBuilder.createWithFlagThresholdsLine("flag-thresholds ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFlagThresholdDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'flag-thresholds' is contained 2 "
+        + "times, but must be contained at most once.");
     VoteBuilder vb = new VoteBuilder();
     vb.flagThresholdsLine = vb.flagThresholdsLine + "\n"
         + vb.flagThresholdsLine;
     vb.buildVote(true);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'dir-source  80550987E1D626E3EBA5E"
+        + "5E75A458DE0626D088C 208.83.223.34 208.83.223.34 443 80' in vote.");
     VoteBuilder.createWithDirSourceLine("dir-source  "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
         + "208.83.223.34 443 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameTooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line 'dir-source "
+        + "urrassssssssssssssssssssssssssssssssssssssssssssssss 80550987E1D626"
+        + "E3EBA5E5E75A458DE0626D088C 208.83.223.34 208.83.223.34 443 80'.");
     VoteBuilder.createWithDirSourceLine("dir-source "
         + "urrassssssssssssssssssssssssssssssssssssssssssssssss "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
         + "208.83.223.34 443 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameIllegalCharacters()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line 'dir-source urra$ "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
+        + "208.83.223.34 443 80'.");
     VoteBuilder.createWithDirSourceLine("dir-source urra$ "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
         + "208.83.223.34 443 80");
@@ -1177,31 +1327,46 @@ public class RelayNetworkStatusVoteImplTest {
         + "208.83.223.34 443 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintTooShort() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'dir-source urras "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D 208.83.223.34 208.83.223.34 "
+        + "443 80'.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "80550987E1D626E3EBA5E5E75A458DE0626D 208.83.223.34 "
         + "208.83.223.34 443 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintTooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'dir-source urras "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D088C8055 208.83.223.34 "
+        + "208.83.223.34 443 80'.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C8055 208.83.223.34 "
         + "208.83.223.34 443 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintIllegalCharacters()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'dir-source urras "
+        + "ABCDEFGHIJKLM6E3EBA5E5E75A458DE0626D088C 208.83.223.34 208.83.223.34"
+        + " 443 80'.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "ABCDEFGHIJKLM6E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
         + "208.83.223.34 443 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'dir-source urras  "
+        + "208.83.223.34 208.83.223.34 443 80' in vote.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + " 208.83.223.34 208.83.223.34 443 80");
   }
@@ -1217,41 +1382,61 @@ public class RelayNetworkStatusVoteImplTest {
     assertEquals("256.256.256.256", vote.getHostname());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testHostnameMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'dir-source urras "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D088C  208.83.223.34 443 80' "
+        + "in vote.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C  208.83.223.34 443 "
         + "80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAddress256()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'256.256.256.256' in line 'dir-source urras "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 256.256.256."
+        + "256 443 80' is not a valid IPv4 address.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
         + "256.256.256.256 443 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAddressMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'dir-source urras "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34  "
+        + "443 80' in vote.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34  443 "
         + "80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirPortMinus443()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'-443' in line 'dir-source urras "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
+        + "208.83.223.34 -443 80' is not a valid port number.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
         + "208.83.223.34 -443 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirPortFourFourThree()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'four-four-three' in line 'dir-source urras "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 208.83.223.34"
+        + " four-four-three 80' is not a valid port number.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
         + "208.83.223.34 four-four-three 80");
@@ -1266,8 +1451,11 @@ public class RelayNetworkStatusVoteImplTest {
         + "208.83.223.34 0 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testOrPortMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'dir-source urras 80550987E1D626E3E"
+        + "BA5E5E75A458DE0626D088C 208.83.223.34 208.83.223.34 443 ' in vote.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
         + "208.83.223.34 443 ");
@@ -1283,15 +1471,21 @@ public class RelayNetworkStatusVoteImplTest {
         + "208.83.223.34 80 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceLineMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'dir-source' is contained 0 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithDirSourceLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSourceLineDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'dir-source' is contained 2 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithDirSourceLine("dir-source urras "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
         + "208.83.223.34 443 80\ndir-source urras "
@@ -1305,24 +1499,33 @@ public class RelayNetworkStatusVoteImplTest {
     VoteBuilder.createWithContactLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testContactLineDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'contact' is contained 2 times, "
+        + "but must be contained at most once.");
     VoteBuilder.createWithContactLine("contact 4096R/E012B42D Jacob "
         + "Appelbaum <jacob at appelbaum.net>\ncontact 4096R/E012B42D Jacob "
         + "Appelbaum <jacob at appelbaum.net>");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testSharedRandParticipateLineDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'shared-rand-participate' is contained "
+        + "2 times, but must be contained at most once.");
     VoteBuilder.createWithSharedRandParticipateLine("shared-rand-participate\n"
         + "shared-rand-participate");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testSharedRandParticipateLineArg()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown
+        .expectMessage("Illegal line 'shared-rand-participate 1' in vote.");
     VoteBuilder.createWithSharedRandParticipateLine(
         "shared-rand-participate 1");
   }
@@ -1334,17 +1537,23 @@ public class RelayNetworkStatusVoteImplTest {
     assertNull(vote.getSharedRandCommitLines());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testSharedRandPreviousValueBeforeNumReveals()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'shared-rand-previous-value "
+        + "grwbnD6I40odtsdtWYxqs0DvPweCur6qG2Fo5p5ivS4= 8' in vote.");
     VoteBuilder.createWithSharedRandPreviousValueLine(
         "shared-rand-previous-value "
         + "grwbnD6I40odtsdtWYxqs0DvPweCur6qG2Fo5p5ivS4= 8");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testSharedRandCurrentNoNumReveals()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'shared-rand-current-value "
+        + "D88plxd8YeLfCIVAR9gjiFlWB1WqpC53kWr350o1pzw=' in vote.");
     VoteBuilder.createWithSharedRandCurrentValueLine(
         "shared-rand-current-value "
             + "D88plxd8YeLfCIVAR9gjiFlWB1WqpC53kWr350o1pzw=");
@@ -1358,102 +1567,146 @@ public class RelayNetworkStatusVoteImplTest {
         vote.getLegacyDirKey());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testLegacyDirKeyLineNoId() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'legacy-dir-key '.");
     VoteBuilder.createWithLegacyDirKeyLine("legacy-dir-key ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirKeyCertificateVersionLineMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'dir-key-certificate-version' is "
+        + "contained 0 times, but must be contained exactly once.");
     VoteBuilder.createWithDirKeyCertificateVersionLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirKeyCertificateVersionLineDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'dir-key-certificate-version' is "
+        + "contained 2 times, but must be contained exactly once.");
     VoteBuilder.createWithDirKeyCertificateVersionLine(
         "dir-key-certificate-version 3\ndir-key-certificate-version 3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintLineMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'fingerprint' is contained 0 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithFingerprintLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintLineDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'fingerprint' is contained 2 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithFingerprintLine("fingerprint "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C\nfingerprint "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintLineTooLong()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'fingerprint "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D088C8055'.");
     VoteBuilder.createWithFingerprintLine("fingerprint "
         + "80550987E1D626E3EBA5E5E75A458DE0626D088C8055");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintLineTooShort()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'fingerprint "
+        + "80550987E1D626E3EBA5E5E75A458DE0626D'.");
     VoteBuilder.createWithFingerprintLine("fingerprint "
         + "80550987E1D626E3EBA5E5E75A458DE0626D");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirKeyPublished3011()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'dir-key-published 3011-04-27 05:34:37'.");
     VoteBuilder.createWithDirKeyPublishedLine("dir-key-published "
         + "3011-04-27 05:34:37");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirKeyPublishedRecentlyAtNoon()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'dir-key-published recently 12:00:00'.");
     VoteBuilder.createWithDirKeyPublishedLine("dir-key-published "
         + "recently 12:00:00");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirKeyPublishedRecentlyNoTime()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'dir-key-published recently' does not "
+        + "contain a timestamp at the expected position.");
     VoteBuilder.createWithDirKeyPublishedLine("dir-key-published "
         + "recently");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirKeyExpiresSoonAtNoon()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal timestamp format in line 'dir-key-expires soon 12:00:00'.");
     VoteBuilder.createWithDirKeyExpiresLine("dir-key-expires "
         + "soon 12:00:00");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirKeyExpiresLineMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'dir-key-expires' is contained 0 times,"
+        + " but must be contained exactly once.");
     VoteBuilder.createWithDirKeyExpiresLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirKeyExpiresLineDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'dir-key-expires' is contained 2 times, "
+        + "but must be contained exactly once.");
     VoteBuilder.createWithDirKeyExpiresLine("dir-key-expires 2012-04-27 "
         + "05:34:37\ndir-key-expires 2012-04-27 05:34:37");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirIdentityKeyLinesMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'dir-identity-key' is contained 0 times,"
+        + " but must be contained exactly once.");
     VoteBuilder.createWithDirIdentityKeyLines(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirSigningKeyLinesMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'dir-signing-key' is contained 0"
+        + " times, but must be contained exactly once.");
     VoteBuilder.createWithDirSigningKeyLines(null);
   }
 
@@ -1463,9 +1716,12 @@ public class RelayNetworkStatusVoteImplTest {
     VoteBuilder.createWithDirKeyCrosscertLines(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirKeyCertificationLinesMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'dir-key-certification' is contained "
+        + "0 times, but must be contained exactly once.");
     VoteBuilder.createWithDirKeyCertificationLines(null);
   }
 
@@ -1475,9 +1731,12 @@ public class RelayNetworkStatusVoteImplTest {
     VoteBuilder.createWithDirectoryFooterLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirectorySignaturesLinesMissing()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'directory-signature' is contained 0 "
+        + "times, but must be contained at least once.");
     VoteBuilder.createWithDirectorySignatureLines(null);
   }
 
@@ -1538,9 +1797,12 @@ public class RelayNetworkStatusVoteImplTest {
     assertEquals(2, vote.getSignatures().size());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUnrecognizedHeaderLineFail()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'unrecognized-line 1' in vote.");
     String unrecognizedLine = "unrecognized-line 1";
     VoteBuilder.createWithUnrecognizedHeaderLine(unrecognizedLine, true);
   }
@@ -1556,9 +1818,12 @@ public class RelayNetworkStatusVoteImplTest {
     assertEquals(unrecognizedLines, vote.getUnrecognizedLines());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUnrecognizedDirSourceLineFail()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'unrecognized-line 1' in vote.");
     String unrecognizedLine = "unrecognized-line 1";
     VoteBuilder.createWithUnrecognizedDirSourceLine(unrecognizedLine,
         true);
@@ -1575,9 +1840,12 @@ public class RelayNetworkStatusVoteImplTest {
     assertEquals(unrecognizedLines, vote.getUnrecognizedLines());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUnrecognizedFooterLineFail()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown
+      .expectMessage("Unrecognized line 'unrecognized-line 1' in vote.");
     String unrecognizedLine = "unrecognized-line 1";
     VoteBuilder.createWithUnrecognizedFooterLine(unrecognizedLine, true);
   }
@@ -1624,9 +1892,11 @@ public class RelayNetworkStatusVoteImplTest {
         vote.getStatusEntry(fingerprint).getMasterKeyEd25519());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testIdRsa1024None()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'id rsa1024 none'.");
     List<String> statusEntries = new ArrayList<>();
     statusEntries.add("r MathematicalApology AAPJIrV9nhfgTYQs0vsTghFaP2A "
         + "uA7p0m68O8ILXsf3aLZUj0EvDNE 2015-12-01 18:01:49 172.99.69.177 "
diff --git a/src/test/java/org/torproject/descriptor/impl/ServerDescriptorImplTest.java b/src/test/java/org/torproject/descriptor/impl/ServerDescriptorImplTest.java
index 7a765d4..70a25a0 100644
--- a/src/test/java/org/torproject/descriptor/impl/ServerDescriptorImplTest.java
+++ b/src/test/java/org/torproject/descriptor/impl/ServerDescriptorImplTest.java
@@ -14,7 +14,9 @@ import org.torproject.descriptor.BridgeServerDescriptor;
 import org.torproject.descriptor.DescriptorParseException;
 import org.torproject.descriptor.ServerDescriptor;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -27,6 +29,9 @@ import java.util.TreeSet;
 /* Test parsing of relay server descriptors. */
 public class ServerDescriptorImplTest {
 
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   /* Helper class to build a descriptor based on default data and
    * modifications requested by test methods. */
   private static class DescriptorBuilder {
@@ -500,8 +505,11 @@ public class ServerDescriptorImplTest {
         descriptor.getDigestSha256Base64());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testRouterLineMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'router' is contained 0 times, but "
+        + "must be contained exactly once.");
     DescriptorBuilder.createWithRouterLine(null);
   }
 
@@ -517,27 +525,39 @@ public class ServerDescriptorImplTest {
     assertEquals(0, (int) descriptor.getDirPort());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testRouterLinePrecedingHibernatingLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'router' must be contained in "
+        + "the first line.");
     DescriptorBuilder.createWithRouterLine("hibernating 1\nrouter "
         + "saberrider2008 94.134.192.243 9001 0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'router  94.134.192.243 9001 0 0' "
+        + "in server descriptor.");
     DescriptorBuilder.createWithRouterLine("router  94.134.192.243 9001 "
         + "0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameInvalidChar() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line "
+        + "'router $aberrider2008 94.134.192.243 9001 0 0'.");
     DescriptorBuilder.createWithRouterLine("router $aberrider2008 "
         + "94.134.192.243 9001 0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNicknameTooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line 'router "
+        + "saberrider2008ReallyLongNickname 94.134.192.243 9001 0 0'.");
     DescriptorBuilder.createWithRouterLine("router "
         + "saberrider2008ReallyLongNickname 94.134.192.243 9001 0 0");
   }
@@ -551,50 +571,74 @@ public class ServerDescriptorImplTest {
     assertEquals("94.134.192.243", descriptor.getAddress());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAddress24() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'94.134.192/24' in line 'router saberrider2008 "
+        + "94.134.192/24 9001 0 0' is not a valid IPv4 address.");
     DescriptorBuilder.createWithRouterLine("router saberrider2008 "
         + "94.134.192/24 9001 0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAddress294() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'294.134.192.243' in line 'router "
+        + "saberrider2008 294.134.192.243 9001 0 0' is not a valid "
+        + "IPv4 address.");
     DescriptorBuilder.createWithRouterLine("router saberrider2008 "
         + "294.134.192.243 9001 0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAddressMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal line 'router saberrider2008  9001 0 0' in server descriptor.");
     DescriptorBuilder.createWithRouterLine("router saberrider2008  9001 "
         + "0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testOrPort99001() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'99001' in line 'router saberrider2008 "
+        + "94.134.192.243 99001 0 0' is not a valid port number.");
     DescriptorBuilder.createWithRouterLine("router saberrider2008 "
         + "94.134.192.243 99001 0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testOrPortMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'router saberrider2008 "
+        + "94.134.192.243  0 0' in server descriptor.");
     DescriptorBuilder.createWithRouterLine("router saberrider2008 "
         + "94.134.192.243  0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testOrPortOne() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'one' in line 'router saberrider2008 "
+        + "94.134.192.243 one 0 0' is not a valid port number.");
     DescriptorBuilder.createWithRouterLine("router saberrider2008 "
         + "94.134.192.243 one 0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testOrPortNewline() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal keyword in line ' 0 0'.");
     DescriptorBuilder.createWithRouterLine("router saberrider2008 "
         + "94.134.192.243 0\n 0 0");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testDirPortMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'router saberrider2008 "
+        + "94.134.192.243 9001 0 ' in server descriptor.");
     DescriptorBuilder.createWithRouterLine("router saberrider2008 "
         + "94.134.192.243 9001 0 ");
   }
@@ -649,15 +693,20 @@ public class ServerDescriptorImplTest {
         descriptor.getCircuitProtocolVersions());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testProtocolsAb() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown
+        .expectMessage("Illegal line 'opt protocols Link A B Circuit 1'.");
     DescriptorBuilder.createWithProtocolsLine("opt protocols Link A B "
         + "Circuit 1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testProtocolsNoCircuitVersions()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'opt protocols Link 1 2'.");
     DescriptorBuilder.createWithProtocolsLine("opt protocols Link 1 2");
   }
 
@@ -671,13 +720,18 @@ public class ServerDescriptorImplTest {
         descriptor.getProtocols().get("Purple"));
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testProtoInvalid() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid line 'proto Invalid=1+2+3'.");
     DescriptorBuilder.createWithProtoLine("proto Invalid=1+2+3");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPublishedMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'published' is contained 0 times, "
+        + "but must be contained exactly once.");
     DescriptorBuilder.createWithPublishedLine(null);
   }
 
@@ -688,26 +742,38 @@ public class ServerDescriptorImplTest {
     assertEquals(1325390599000L, descriptor.getPublishedMillis());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPublished2039() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal timestamp format in line 'published 2039-01-01 04:03:19'.");
     DescriptorBuilder.createWithPublishedLine("published 2039-01-01 "
         + "04:03:19");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPublished1912() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'published 1912-01-01 04:03:19'.");
     DescriptorBuilder.createWithPublishedLine("published 1912-01-01 "
         + "04:03:19");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPublishedFeb31() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'published 2012-02-31 04:03:19'.");
     DescriptorBuilder.createWithPublishedLine("published 2012-02-31 "
         + "04:03:19");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testPublishedNoTime() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Line 'published 2012-01-01' does not contain "
+        + "a timestamp at the expected position.");
     DescriptorBuilder.createWithPublishedLine("published 2012-01-01");
   }
 
@@ -727,26 +793,38 @@ public class ServerDescriptorImplTest {
         descriptor.getFingerprint());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintG() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'opt fingerprint "
+        + "G873 3048 FC8E C910 2466 AD8F 3098 622B F1BF 71FD'.");
     DescriptorBuilder.createWithFingerprintLine("opt fingerprint G873 "
         + "3048 FC8E C910 2466 AD8F 3098 622B F1BF 71FD");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintTooShort() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'opt fingerprint D873 3048 FC8E "
+        + "C910 2466 AD8F 3098 622B F1BF'.");
     DescriptorBuilder.createWithFingerprintLine("opt fingerprint D873 "
         + "3048 FC8E C910 2466 AD8F 3098 622B F1BF");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintTooLong() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'opt fingerprint D873 3048 "
+        + "FC8E C910 2466 AD8F 3098 622B F1BF 71FD D873'.");
     DescriptorBuilder.createWithFingerprintLine("opt fingerprint D873 "
         + "3048 FC8E C910 2466 AD8F 3098 622B F1BF 71FD D873");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFingerprintNoSpaces() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'opt fingerprint "
+        + "D8733048FC8EC9102466AD8F3098622BF1BF71FD'.");
     DescriptorBuilder.createWithFingerprintLine("opt fingerprint "
         + "D8733048FC8EC9102466AD8F3098622BF1BF71FD");
   }
@@ -765,8 +843,10 @@ public class ServerDescriptorImplTest {
     assertEquals(48, descriptor.getUptime().longValue());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUptimeFourtyEight() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal value in line 'uptime fourty-eight'.");
     DescriptorBuilder.createWithUptimeLine("uptime fourty-eight");
   }
 
@@ -775,18 +855,24 @@ public class ServerDescriptorImplTest {
     DescriptorBuilder.createWithUptimeLine("uptime -1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUptimeSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Wrong number of values in line 'uptime '.");
     DescriptorBuilder.createWithUptimeLine("uptime ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUptimeNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Wrong number of values in line 'uptime'.");
     DescriptorBuilder.createWithUptimeLine("uptime");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUptimeFourEight() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Wrong number of values in line 'uptime 4 8'.");
     DescriptorBuilder.createWithUptimeLine("uptime 4 8");
   }
 
@@ -799,13 +885,19 @@ public class ServerDescriptorImplTest {
     assertEquals(53470, (int) descriptor.getBandwidthObserved());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBandwidthMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'bandwidth' is contained 0 times, "
+        + "but must be contained exactly once.");
     DescriptorBuilder.createWithBandwidthLine(null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBandwidthOneValue() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown
+        .expectMessage("Wrong number of values in line 'bandwidth 51200'.");
     DescriptorBuilder.createWithBandwidthLine("bandwidth 51200");
   }
 
@@ -821,15 +913,20 @@ public class ServerDescriptorImplTest {
     assertEquals(-1, (int) descriptor.getBandwidthObserved());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBandwidthFourValues() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Wrong number of values in line "
+        + "'bandwidth 51200 51200 53470 53470'.");
     DescriptorBuilder.createWithBandwidthLine("bandwidth 51200 51200 "
         + "53470 53470");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testBandwidthMinusOneTwoThree()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal values in line 'bandwidth -1 -2 -3'.");
     DescriptorBuilder.createWithBandwidthLine("bandwidth -1 -2 -3");
   }
 
@@ -842,23 +939,31 @@ public class ServerDescriptorImplTest {
         descriptor.getExtraInfoDigestSha1Hex());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExtraInfoDigestNoSpace()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'opt extra-info-digest'.");
     DescriptorBuilder.createWithExtraInfoDigestLine("opt "
         + "extra-info-digest");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExtraInfoDigestTooShort()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'opt "
+        + "extra-info-digest 1469D1550738A25B1E7B47CDDBCD7B2899F5'.");
     DescriptorBuilder.createWithExtraInfoDigestLine("opt "
         + "extra-info-digest 1469D1550738A25B1E7B47CDDBCD7B2899F5");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExtraInfoDigestTooLong()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal hex string in line 'opt "
+        + "extra-info-digest 1469D1550738A25B1E7B47CDDBCD7B2899F51B741469'.");
     DescriptorBuilder.createWithExtraInfoDigestLine("opt "
         + "extra-info-digest "
         + "1469D1550738A25B1E7B47CDDBCD7B2899F51B741469");
@@ -947,8 +1052,11 @@ public class ServerDescriptorImplTest {
     assertEquals("Random Person", descriptor.getContact());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testContactDuplicate() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'contact' is contained 2 times, "
+        + "but must be contained at most once.");
     DescriptorBuilder.createWithContactLine("contact Random "
         + "Person\ncontact Random Person");
   }
@@ -992,8 +1100,11 @@ public class ServerDescriptorImplTest {
         descriptor.getExitPolicyLines());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExitPolicyNoPort() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'*' in line 'reject *' must contain address "
+        + "and port.");
     DescriptorBuilder.createWithExitPolicyLines("reject *");
   }
 
@@ -1006,25 +1117,37 @@ public class ServerDescriptorImplTest {
         "reject *:*"}), descriptor.getExitPolicyLines());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExitPolicyReject321() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'123.123.123.321' in line 'reject "
+        + "123.123.123.321:80' is not a valid IPv4 address.");
     DescriptorBuilder.createWithExitPolicyLines("reject "
         + "123.123.123.321:80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExitPolicyRejectPort66666()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("'66666' in line 'reject *:66666' "
+        + "is not a valid port number.");
     DescriptorBuilder.createWithExitPolicyLines("reject *:66666");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExitPolicyProjectAll() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'project *:*' in server descriptor.");
     DescriptorBuilder.createWithExitPolicyLines("project *:*");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testExitPolicyMissing() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Either keyword 'accept' or 'reject' must be "
+        + "contained at least once.");
     DescriptorBuilder.createWithExitPolicyLines(null);
   }
 
@@ -1048,9 +1171,12 @@ public class ServerDescriptorImplTest {
         + "-----END SIGNATURE----");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testRouterSignatureNotLastLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'contact' is contained 2 times, "
+        + "but must be contained at most once.");
     DescriptorBuilder.createWithRouterSignatureLines("router-signature\n"
         + "-----BEGIN SIGNATURE-----\n"
         + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT"
@@ -1080,13 +1206,17 @@ public class ServerDescriptorImplTest {
     assertTrue(descriptor.isHibernating());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testHibernatingYep() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'hibernating yep'.");
     DescriptorBuilder.createWithHibernatingLine("hibernating yep");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testHibernatingNoSpace() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'hibernating'.");
     DescriptorBuilder.createWithHibernatingLine("hibernating");
   }
 
@@ -1116,20 +1246,29 @@ public class ServerDescriptorImplTest {
         descriptor.getFamilyEntries());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFamilyDuplicate() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'family' is contained 2 times, "
+        + "but must be contained at most once.");
     DescriptorBuilder.createWithFamilyLine("family "
         + "saberrider2008\nfamily saberrider2008");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFamilyNicknamePrefix() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Illegal hex string in line 'family $saberrider2008'.");
     DescriptorBuilder.createWithFamilyLine("family $saberrider2008");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testFamilyFingerprintNoPrefix()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal nickname in line 'family "
+        + "D8733048FC8EC9102466AD8F3098622BF1BF71FD'.");
     DescriptorBuilder.createWithFamilyLine("family "
         + "D8733048FC8EC9102466AD8F3098622BF1BF71FD");
   }
@@ -1185,43 +1324,64 @@ public class ServerDescriptorImplTest {
     assertNotNull(descriptor.getWriteHistory());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWriteHistory3012() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'write-history 3012-01-01 03:51:44 (900 s) "
+        + "4345856,261120,7591936,1748992'.");
     DescriptorBuilder.createWithWriteHistoryLine("write-history "
         + "3012-01-01 03:51:44 (900 s) 4345856,261120,7591936,1748992");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWriteHistoryNoSeconds()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal timestamp format in line "
+        + "'write-history 2012-01-01 03:51 (900 s) 4345856,261120,"
+        + "7591936,1748992'.");
     DescriptorBuilder.createWithWriteHistoryLine("write-history "
         + "2012-01-01 03:51 (900 s) 4345856,261120,7591936,1748992");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWriteHistoryNoParathenses()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid bandwidth-history line 'write-history "
+        + "2012-01-01 03:51:44 900 s 4345856,261120,7591936,1748992'.");
     DescriptorBuilder.createWithWriteHistoryLine("write-history "
         + "2012-01-01 03:51:44 900 s 4345856,261120,7591936,1748992");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWriteHistoryNoSpaceSeconds()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid bandwidth-history line "
+        + "'write-history 2012-01-01 03:51:44 (900s) "
+        + "4345856,261120,7591936,1748992'.");
     DescriptorBuilder.createWithWriteHistoryLine("write-history "
         + "2012-01-01 03:51:44 (900s) 4345856,261120,7591936,1748992");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWriteHistoryTrailingComma()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid bandwidth-history line 'write-history "
+        + "2012-01-01 03:51:44 (900 s) 4345856,261120,7591936,'.");
     DescriptorBuilder.createWithWriteHistoryLine("write-history "
         + "2012-01-01 03:51:44 (900 s) 4345856,261120,7591936,");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWriteHistoryOneTwoThree()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid bandwidth-history line 'write-history "
+        + "2012-01-01 03:51:44 (900 s) one,two,three'.");
     DescriptorBuilder.createWithWriteHistoryLine("write-history "
         + "2012-01-01 03:51:44 (900 s) one,two,three");
   }
@@ -1250,8 +1410,11 @@ public class ServerDescriptorImplTest {
         .isEmpty());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testWriteHistoryNoS() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Invalid bandwidth-history line "
+        + "'write-history 2012-01-01 03:51:44 (900 '.");
     DescriptorBuilder.createWithWriteHistoryLine(
         "write-history 2012-01-01 03:51:44 (900 ");
   }
@@ -1324,13 +1487,17 @@ public class ServerDescriptorImplTest {
     assertFalse(descriptor.getUsesEnhancedDnsLogic());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEventdnsTrue() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'eventdns true'.");
     DescriptorBuilder.createWithEventdnsLine("eventdns true");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEventdnsNo() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'eventdns no'.");
     DescriptorBuilder.createWithEventdnsLine("eventdns no");
   }
 
@@ -1349,8 +1516,10 @@ public class ServerDescriptorImplTest {
     assertTrue(descriptor.getCachesExtraInfo());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testCachesExtraInfoTrue() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'caches-extra-info true'.");
     DescriptorBuilder.createWithCachesExtraInfoLine("caches-extra-info "
         + "true");
   }
@@ -1371,16 +1540,22 @@ public class ServerDescriptorImplTest {
     assertTrue(descriptor.getAllowSingleHopExits());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAllowSingleHopExitsTrue()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'allow-single-hop-exits true'.");
     DescriptorBuilder.createWithAllowSingleHopExitsLine(
         "allow-single-hop-exits true");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testAllowSingleHopExitsNonAsciiKeyword()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized character in keyword "
+        + "'�allow-single-hop-exits' in line "
+        + "'�allow-single-hop-exits'.");
     DescriptorBuilder.createWithNonAsciiLineBytes(new byte[] {
         0x14, (byte) 0xfe, 0x18,                  // non-ascii chars
         0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x2d,       // "allow-"
@@ -1398,32 +1573,43 @@ public class ServerDescriptorImplTest {
     assertEquals("80,1194,1220,1293", descriptor.getIpv6PortList());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testIpv6PolicyLineNoPolicy()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'ipv6-policy 80'.");
     DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testIpv6PolicyLineNoPorts()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'ipv6-policy accept'.");
     DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy accept");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testIpv6PolicyLineNoPolicyNoPorts()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'ipv6-policy '.");
     DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testIpv6PolicyLineProject()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'ipv6-policy project 80'.");
     DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy project 80");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testTwoIpv6PolicyLines() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'ipv6-policy' is contained 2 times, "
+        + "but must be contained at most once.");
     DescriptorBuilder.createWithIpv6PolicyLine(
         "ipv6-policy accept 80,1194,1220,1293\n"
         + "ipv6-policy accept 80,1194,1220,1293");
@@ -1448,22 +1634,30 @@ public class ServerDescriptorImplTest {
         descriptor.getNtorOnionKey());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNtorOnionKeyLineNoKey()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'ntor-onion-key '.");
     DescriptorBuilder.createWithNtorOnionKeyLine("ntor-onion-key ");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNtorOnionKeyLineTwoKeys()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'ntor-onion-key Y/XgaHcPIJVa"
+        + "4D55kir9QLH8rEYAaLXuv3c3sm8jYhY Y/XgaHcPIJVa4D55kir9QLH8rEYAa"
+        + "LXuv3c3sm8jYhY'.");
     DescriptorBuilder.createWithNtorOnionKeyLine("ntor-onion-key "
         + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY "
         + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testTwoNtorOnionKeyLines() throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Blank lines are not allowed.");
     DescriptorBuilder.createWithNtorOnionKeyLine("ntor-onion-key "
         + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY\nntor-onion-key "
         + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY\n");
@@ -1485,31 +1679,42 @@ public class ServerDescriptorImplTest {
     assertFalse(descriptor.getTunnelledDirServer());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testTunnelledDirServerTypo()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line 'tunneled-dir-server' "
+        + "in server descriptor.");
     DescriptorBuilder.createWithTunnelledDirServerLine(
         "tunneled-dir-server");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testTunnelledDirServerTwice()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'tunnelled-dir-server' is contained "
+        + "2 times, but must be contained at most once.");
     DescriptorBuilder.createWithTunnelledDirServerLine(
         "tunnelled-dir-server\ntunnelled-dir-server");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testTunnelledDirServerArgs()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Illegal line 'tunnelled-dir-server 1'.");
     DescriptorBuilder.createWithTunnelledDirServerLine(
         "tunnelled-dir-server 1");
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testUnrecognizedLineFail()
       throws DescriptorParseException {
     String unrecognizedLine = "unrecognized-line 1";
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage(
+        "Unrecognized line 'unrecognized-line 1' in server descriptor.");
     DescriptorBuilder.createWithUnrecognizedLine(unrecognizedLine, true);
   }
 
@@ -1598,9 +1803,12 @@ public class ServerDescriptorImplTest {
         descriptor.getRouterSignatureEd25519());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519IdentityMasterKeyMismatch()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Mismatch between identity-ed25519 and "
+        + "master-key-ed25519.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
         "master-key-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
         ROUTER_SIG_ED25519_LINE);
@@ -1613,17 +1821,23 @@ public class ServerDescriptorImplTest {
         MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519IdentityDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'identity-ed25519' is contained 2 "
+        + "times, but must be contained at most once.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES + "\n"
         + IDENTITY_ED25519_LINES, MASTER_KEY_ED25519_LINE,
         ROUTER_SIG_ED25519_LINE);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519IdentityEmptyCrypto()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown
+        .expectMessage("Invalid length of identity-ed25519 (in bytes): 0");
     DescriptorBuilder.createWithEd25519Lines("identity-ed25519\n"
         + "-----BEGIN ED25519 CERT-----\n-----END ED25519 CERT-----",
         MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
@@ -1640,9 +1854,12 @@ public class ServerDescriptorImplTest {
         descriptor.getMasterKeyEd25519());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519MasterKeyDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'master-key-ed25519' is contained 2 "
+        + "times, but must be contained at most once.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
         MASTER_KEY_ED25519_LINE + "\n" + MASTER_KEY_ED25519_LINE,
         ROUTER_SIG_ED25519_LINE);
@@ -1655,17 +1872,23 @@ public class ServerDescriptorImplTest {
         MASTER_KEY_ED25519_LINE, null);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519RouterSigDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'router-sig-ed25519' is contained 2 "
+        + "times, but must be contained at most once.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
         MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE + "\n"
         + ROUTER_SIG_ED25519_LINE);
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testEd25519FollowedbyUnrecognizedLine()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Unrecognized line 'unrecognized-line 1' "
+        + "in server descriptor.");
     DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
         MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE
         + "\nunrecognized-line 1");
@@ -1697,9 +1920,12 @@ public class ServerDescriptorImplTest {
         descriptor.getOnionKeyCrosscert());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testOnionKeyCrosscertDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'onion-key-crosscert' is contained 2 "
+        + "times, but must be contained at most once.");
     DescriptorBuilder.createWithOnionKeyCrosscertLines(
         ONION_KEY_CROSSCERT_LINES + "\n" + ONION_KEY_CROSSCERT_LINES);
   }
@@ -1716,9 +1942,12 @@ public class ServerDescriptorImplTest {
     assertEquals(1, descriptor.getNtorOnionKeyCrosscertSign());
   }
 
-  @Test(expected = DescriptorParseException.class)
+  @Test
   public void testNtorOnionKeyCrosscertDuplicate()
       throws DescriptorParseException {
+    this.thrown.expect(DescriptorParseException.class);
+    this.thrown.expectMessage("Keyword 'ntor-onion-key-crosscert' is "
+        + "contained 2 times, but must be contained at most once.");
     DescriptorBuilder.createWithOnionKeyCrosscertLines(
         NTOR_ONION_KEY_CROSSCERT_LINES + "\n"
         + NTOR_ONION_KEY_CROSSCERT_LINES);





More information about the tor-commits mailing list