jasonish: Do you have a sample of this log file we can include for tests?
ArthanJans: [u2logs.zip](https://github.com/jasonish/py-idstools/files/3276982/u2logs.zip)
Here are some sample log files for testing. Be warned that I have not checked that they are exhaustive and there may be some things that remain untested
diff --git a/idstools/rule.py b/idstools/rule.py
index 17a5231..d0af8da 100644
--- a/idstools/rule.py
+++ b/idstools/rule.py
@@ -53,7 +53,7 @@ rule_pattern = re.compile(r"^(?P<enabled>#)*[\s#]*"
 
 # Rule actions we expect to see.
 actions = (
-    "alert", "log", "pass", "activate", "dynamic", "drop", "reject", "sdrop")
+    "alert", "config", "log", "pass", "activate", "dynamic", "drop", "reject", "sdrop")
 
 class Rule(dict):
     """Class representing a rule.
@@ -67,7 +67,12 @@ class Rule(dict):
       disabled (commented)
     - **action**: The action of the rule (alert, pass, etc) as a
       string
+    - **proto**: The protocol string of the rule.
+    - **source_addr**: The source address string of the rule.
+    - **source_port**: The source ports string of the rule.
     - **direction**: The direction string of the rule.
+    - **dest_addr**: The destination address string of the rule.
+    - **dest_port**: The destination ports string of the rule.
     - **gid**: The gid of the rule as an integer
     - **sid**: The sid of the rule as an integer
     - **rev**: The revision of the rule as an integer
@@ -84,12 +89,16 @@ class Rule(dict):
     :param group: Optional parameter to set the group (filename) of the rule
 
     """
-
     def __init__(self, enabled=None, action=None, group=None):
         dict.__init__(self)
         self["enabled"] = enabled
         self["action"] = action
+        self["proto"] = None
+        self["source_addr"] = None
+        self["source_port"] = None
         self["direction"] = None
+        self["dest_addr"] = None
+        self["dest_port"] = None
         self["group"] = group
         self["gid"] = 1
         self["sid"] = None
@@ -216,7 +225,12 @@ def parse(buf, group=None):
     # If a decoder rule, the header will be one word.
     if len(header.split(" ")) == 1:
         action = header
+        proto = None
+        source_addr = None
+        source_port = None
         direction = None
+        dest_addr = None
+        dest_port = None
     else:
         states = ["action",
                   "proto",
@@ -269,8 +283,13 @@ def parse(buf, group=None):
         return None
 
     rule = Rule(enabled=enabled, action=action, group=group)
-    rule["direction"] = direction
     rule["header"] = header
+    rule["proto"] = proto
+    rule["source_addr"] = source_addr
+    rule["source_port"] = source_port
+    rule["direction"] = direction
+    rule["dest_addr"] = dest_addr
+    rule["dest_port"] = dest_port
 
     options = m.group("options")
 
diff --git a/idstools/scripts/eve2pcap.py b/idstools/scripts/eve2pcap.py
index d5452b4..dcd7a7d 100644
--- a/idstools/scripts/eve2pcap.py
+++ b/idstools/scripts/eve2pcap.py
@@ -160,7 +160,10 @@ def payload2packet(event):
     if not "payload" in event:
         return None, None
     payload = base64.b64decode(event["payload"])
-    packet = IP(src=event["src_ip"], dst=event["dest_ip"])
+    if ':' in event["src_ip"]:
+        packet = IPv6(src=event["src_ip"], dst=event["dest_ip"])
+    else:
+        packet = IP(src=event["src_ip"], dst=event["dest_ip"])
     if event["proto"] == "TCP":
         packet = packet / TCP(sport=event["src_port"], dport=event["dest_port"])
     elif event["proto"] == "UDP":
diff --git a/idstools/scripts/rulecat.py b/idstools/scripts/rulecat.py
index 1727787..3017a0e 100644
--- a/idstools/scripts/rulecat.py
+++ b/idstools/scripts/rulecat.py
@@ -256,6 +256,9 @@ class Fetch(object):
     def check_checksum(self, tmp_filename, url):
         try:
             checksum_url = url + ".md5"
+            url_parts = url.split("?")
+            if len(url_parts) == 2:
+                checksum_url = ".md5?".join(url_parts)
             local_checksum = hashlib.md5(
                 open(tmp_filename, "rb").read()).hexdigest().strip()
             remote_checksum_buf = io.BytesIO()
@@ -398,7 +401,7 @@ def load_matchers(filename):
                 continue
             matcher = parse_rule_match(line)
             if not matcher:
-                logger.warn("Failed to parse: \"%s\"" % (line))
+                logger.warning("Failed to parse: \"%s\"" % (line))
             else:
                 matchers.append(matcher)
 
@@ -420,7 +423,7 @@ def load_local(local, files):
             logger.info("Loading local file %s" % (filename))
             basename = os.path.basename(filename)
             if basename in files:
-                logger.warn(
+                logger.warning(
                     "Local file %s overrides existing file of same name." % (
                         filename))
             files[basename] = open(filename, "rb").read()
@@ -811,7 +814,7 @@ def main():
             logger.info("Found Suricata version %s at %s." % (
                 str(suricata_version.full), args.suricata))
         else:
-            logger.warn("Failed to get Suricata version, using %s",
+            logger.warning("Failed to get Suricata version, using %s",
                         DEFAULT_SURICATA_VERSION)
     if suricata_version is None:
         suricata_version = idstools.suricata.parse_version(
diff --git a/idstools/scripts/u2eve.py b/idstools/scripts/u2eve.py
index 3d7ba57..57190e7 100644
--- a/idstools/scripts/u2eve.py
+++ b/idstools/scripts/u2eve.py
@@ -362,12 +362,12 @@ def main():
         msgmap.load_signature_map(open(os.path.expanduser(args.sidmsgmap_path)))
 
     if msgmap.size() == 0:
-        LOG.warn("WARNING: No alert message map entries loaded.")
+        LOG.warning("WARNING: No alert message map entries loaded.")
     else:
         LOG.info("Loaded %s rule message map entries.", msgmap.size())
 
     if classmap.size() == 0:
-        LOG.warn("WARNING: No classifications loaded.")
+        LOG.warning("WARNING: No classifications loaded.")
     else:
         LOG.info("Loaded %s classifications.", classmap.size())
 
diff --git a/idstools/scripts/u2fast.py b/idstools/scripts/u2fast.py
index 1228b5d..9e7ee7d 100644
--- a/idstools/scripts/u2fast.py
+++ b/idstools/scripts/u2fast.py
@@ -157,12 +157,12 @@ def main():
         msgmap.load_signature_map(open(os.path.expanduser(args.sidmsgmap_path)))
 
     if msgmap.size() == 0:
-        LOG.warn("WARNING: No alert message map entries loaded.")
+        LOG.warning("WARNING: No alert message map entries loaded.")
     else:
         LOG.info("Loaded %s rule message map entries.", msgmap.size())
 
     if classmap.size() == 0:
-        LOG.warn("WARNING: No classifications loaded.")
+        LOG.warning("WARNING: No classifications loaded.")
     else:
         LOG.info("Loaded %s classifications.", classmap.size())
 
diff --git a/idstools/scripts/u2json.py b/idstools/scripts/u2json.py
index 27d2866..f3fe08b 100644
--- a/idstools/scripts/u2json.py
+++ b/idstools/scripts/u2json.py
@@ -309,12 +309,12 @@ def main():
         msgmap.load_signature_map(open(os.path.expanduser(args.sidmsgmap_path)))
 
     if msgmap.size() == 0:
-        LOG.warn("No alert message map entries loaded.")
+        LOG.warning("No alert message map entries loaded.")
     else:
         LOG.info("Loaded %s rule message map entries.", msgmap.size())
 
     if classmap.size() == 0:
-        LOG.warn("No classifications loaded.")
+        LOG.warning("No classifications loaded.")
     else:
         LOG.info("Loaded %s classifications.", classmap.size())
 
diff --git a/idstools/snort.py b/idstools/snort.py
index 9fe383b..c515c55 100644
--- a/idstools/snort.py
+++ b/idstools/snort.py
@@ -119,7 +119,7 @@ class SnortApp(object):
     def dump_dynamic_rules(self, dynamic_detection_lib_dir, verbose=False):
 
         if not self.exists():
-            LOG.warn("Snort application not set or does not exist")
+            LOG.warning("Snort application not set or does not exist")
             return
 
         destination = idstools.util.mktempdir()
diff --git a/idstools/unified2.py b/idstools/unified2.py
index 4d60ffc..5b9d9b6 100644
--- a/idstools/unified2.py
+++ b/idstools/unified2.py
@@ -68,6 +68,7 @@ EXTRA_DATA      = 110
 EVENT_APPID     = 111
 EVENT_APPID_IP6 = 112
 APPSTAT         = 113
+EVENT_V3        = 114
 
 RECORD_TYPES = [
     PACKET,
@@ -79,6 +80,7 @@ RECORD_TYPES = [
     EVENT_APPID,
     EVENT_APPID_IP6,
     APPSTAT,
+    EVENT_V3,
 ]
 
 EXTRA_DATA_TYPE = {
@@ -203,6 +205,33 @@ EVENT_V2_FIELDS = EVENT_FIELDS + (
     Field("pad2", 2),
 )
 
+EVENT_V3_FIELDS = (
+    Field("sensor-id", 4),
+    Field("event-id", 4),
+    Field("event-second", 4),
+    Field("event-microsecond", 4),
+    Field("generator-id", 4),
+    Field("signature-id", 4),
+    Field("signature-revision", 4),
+    Field("classification-id", 4),
+    Field("priority", 4),
+    Field("pid-context", 4),
+    Field("pid-inspect", 4),
+    Field("pid-detect", 4),
+    Field("source-ip.raw", 16, "16s"),
+    Field("destination-ip.raw", 16, "16s"),
+    Field("mpls-label", 4),
+    Field("sport-itype", 2),
+    Field("dport-itype", 2),
+    Field("vlan-id", 2),
+    Field("unused", 2),
+    Field("ip-version", 1),
+    Field("ip-protocol", 1),
+    Field("status", 1),
+    Field("action", 1),
+    Field("appid", None),
+)
+
 # Fields for an IPv6 v2 event.
 EVENT_V2_IP6_FIELDS = EVENT_IP6_FIELDS + (
     Field("mpls-label", 4),
@@ -374,8 +403,12 @@ class EventDecoder(AbstractDecoder):
         values = struct.unpack(self.format, buf[0:self.fixed_len])
         keys = [field.name for field in self.fields]
         event = dict(zip(keys, values))
-        event["source-ip"] = self.decode_ip(event["source-ip.raw"])
-        event["destination-ip"] = self.decode_ip(event["destination-ip.raw"])
+        if self.fields == EVENT_V3_FIELDS:
+            event["source-ip"] = self.decode_ip_v3(event["source-ip.raw"], event["ip-version"] >> 4)
+            event["destination-ip"] = self.decode_ip_v3(event["destination-ip.raw"], event["ip-version"] & 15)
+        else:
+            event["source-ip"] = self.decode_ip(event["source-ip.raw"])
+            event["destination-ip"] = self.decode_ip(event["destination-ip.raw"])
 
         # Check for remaining data, the appid.
         remainder = buf[self.fixed_len:]
@@ -391,6 +424,13 @@ class EventDecoder(AbstractDecoder):
             parts = struct.unpack(">" + "H" * int((len(addr) / 2)), addr)
             return ":".join("%04x" % p for p in parts)
 
+    def decode_ip_v3(self, addr, vers):
+        if vers == 4:
+            return socket.inet_ntop(socket.AF_INET, addr[12:])
+        else:
+            parts = struct.unpack(">" + "H" * int((len(addr) / 2)), addr)
+            return ":".join("%04x" % p for p in parts)
+
 class PacketDecoder(AbstractDecoder):
     """ Decoder for packet type records. """
 
@@ -417,6 +457,7 @@ DECODERS = {
     EVENT_APPID_IP6: EventDecoder(EVENT_V2_IP6_FIELDS),
     PACKET:          PacketDecoder(PACKET_FIELDS),
     EXTRA_DATA:      ExtraDataDecoder(EXTRA_DATA_FIELDS),
+    EVENT_V3:        EventDecoder(EVENT_V3_FIELDS),
 }
 
 class Aggregator(object):
@@ -430,7 +471,7 @@ class Aggregator(object):
     """
 
     def __init__(self):
-        LOG.warn("idstools.unified2.Aggregator has been deprecated")
+        LOG.warning("idstools.unified2.Aggregator has been deprecated")
         self.queue = collections.deque()
 
     def add(self, record):
@@ -452,9 +493,9 @@ class Aggregator(object):
             if record["event-id"] == self.queue[-1]["event-id"]:
                 self.queue.append(record)
             else:
-                LOG.warn("Record not associated with current event, discarding.")
+                LOG.warning("Record not associated with current event, discarding.")
         else:
-            LOG.warn("Discarding non-event type while not in event context.")
+            LOG.warning("Discarding non-event type while not in event context.")
         return event
 
     def flush(self):
@@ -679,7 +720,7 @@ class FileEventReader(object):
     """
 
     def __init__(self, *files):
-        LOG.warn("idstools.unified2.FileEventReader has been deprecated")
+        LOG.warning("idstools.unified2.FileEventReader has been deprecated")
         self.reader = FileRecordReader(*files)
         self.aggregator = Aggregator()
 
@@ -886,7 +927,7 @@ class SpoolEventReader(object):
 
     def __init__(self, directory, prefix, follow=False, delete=False,
                  bookmark=False):
-        LOG.warn("idstools.unified2.SpoolEventReader has been deprecated")
+        LOG.warning("idstools.unified2.SpoolEventReader has been deprecated")
 
         self.follow = follow
         self.delete = delete
diff --git a/tox.ini b/tox.ini
index 33dc40a..8d366cc 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@
 # and then run "tox" from this directory.
 
 [tox]
-envlist = py27, py34, py35, py36
+envlist = py27, py36
 
 [testenv]
 commands = pytest

Recent versions of Snort unified2 not supported.
Hello,

I am trying to convert snort (=2.9.11) log to json file but getting error on python 2.7.12 with idstools (=0.6.3)

the code:

`
#!/usr/bin/python
from idstools import unified2

reader = unified2.SpoolRecordReader("/var/log/snort",
    "snort.log", follow=True)
for record in reader:
    if isinstance(record, unified2.Event):
        print("Event:")
    elif isinstance(record, unified2.Packet):
        print("Packet:")
    elif isinstance(record, unified2.ExtraData):
        print("Extra-Data:")
    print(record)
`

error:
**
Traceback (most recent call last):
  File "/home/zero/Desktop/log_2_json/for_snort.py", line 6, in <module>
    for record in reader:
  File "/home/zero/.local/lib/python2.7/site-packages/idstools/unified2.py", line 846, in iter_next
    record = self.next()
  File "/home/zero/.local/lib/python2.7/site-packages/idstools/unified2.py", line 817, in next
    record = self.reader.next()
  File "/home/zero/.local/lib/python2.7/site-packages/idstools/unified2.py", line 610, in next
    return read_record(self.fileobj)
  File "/home/zero/.local/lib/python2.7/site-packages/idstools/unified2.py", line 571, in read_record
    raise UnknownRecordType(rtype)
idstools.unified2.UnknownRecordType: Unknown record type: 3569595041
**

my snort log directory have files:
**
ls /var/log/snort
archived_logs  snort.log.1547718241  snort.log.1547725234
**
help me man.
diff --git a/tests/test_classificationmap.py b/tests/test_classificationmap.py
index a7be647..133e4be 100644
--- a/tests/test_classificationmap.py
+++ b/tests/test_classificationmap.py
@@ -7,24 +7,25 @@ class ClassificationMapTestCase(unittest.TestCase):
     test_filename = "tests/classification.config"
 
     def test_load_from_file(self):
-        m = ClassificationMap(open(self.test_filename))
+        with open(self.test_filename) as test_file:
+            m = ClassificationMap(test_file)
 
         # Classifications are indexed at 1.
-        self.assertEquals(None, m.get(0))
+        self.assertEqual(None, m.get(0))
 
         c = m.get(1)
-        self.assertEquals("not-suspicious", c["name"])
-        self.assertEquals("Not Suspicious Traffic", c["description"])
-        self.assertEquals(3, c["priority"])
+        self.assertEqual("not-suspicious", c["name"])
+        self.assertEqual("Not Suspicious Traffic", c["description"])
+        self.assertEqual(3, c["priority"])
 
         c = m.get(34)
-        self.assertEquals("default-login-attempt", c["name"])
-        self.assertEquals("Attempt to Login By a Default Username and Password",
+        self.assertEqual("default-login-attempt", c["name"])
+        self.assertEqual("Attempt to Login By a Default Username and Password",
                           c["description"])
-        self.assertEquals(2, c["priority"])
+        self.assertEqual(2, c["priority"])
 
         c = m.get_by_name("unknown")
         self.assertTrue(c is not None)
-        self.assertEquals("unknown", c["name"])
-        self.assertEquals("Unknown Traffic", c["description"])
-        self.assertEquals(3, c["priority"])
+        self.assertEqual("unknown", c["name"])
+        self.assertEqual("Unknown Traffic", c["description"])
+        self.assertEqual(3, c["priority"])
diff --git a/tests/test_packet.py b/tests/test_packet.py
index ec9024f..f4f59c2 100644
--- a/tests/test_packet.py
+++ b/tests/test_packet.py
@@ -35,7 +35,8 @@ from idstools import packet
 class TestIPv6WithExtensionHeader(unittest.TestCase):
 
     def setUp(self):
-        self.packet = open("tests/NPD_2_1_2_1_Type_0.pcap", "rb").read()[40:]
+        with open("tests/NPD_2_1_2_1_Type_0.pcap", "rb") as file:
+            self.packet = file.read()[40:]
 
     def test_decode(self):
         decoded = packet.decode_ethernet(self.packet)
diff --git a/tests/test_rule.py b/tests/test_rule.py
index eb17211..8ef424c 100644
--- a/tests/test_rule.py
+++ b/tests/test_rule.py
@@ -40,7 +40,12 @@ class RuleTestCase(unittest.TestCase):
         rule = idstools.rule.parse("""alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS (msg:"ET CURRENT_EVENTS Request to .in FakeAV Campaign June 19 2012 exe or zip"; flow:established,to_server; content:"setup."; fast_pattern:only; http_uri; content:".in|0d 0a|"; flowbits:isset,somebit; flowbits:unset,otherbit; http_header; pcre:"/\/[a-f0-9]{16}\/([a-z0-9]{1,3}\/)?setup\.(exe|zip)$/U"; pcre:"/^Host\x3a\s.+\.in\r?$/Hmi"; metadata:stage,hostile_download; reference:url,isc.sans.edu/diary/+Vulnerabilityqueerprocessbrittleness/13501; classtype:trojan-activity; sid:2014929; rev:1;)""")
         self.assertEqual(rule.enabled, True)
         self.assertEqual(rule.action, "alert")
+        self.assertEqual(rule.proto, "tcp")
+        self.assertEqual(rule.source_addr, "$HOME_NET")
+        self.assertEqual(rule.source_port, "any")
         self.assertEqual(rule.direction, "->")
+        self.assertEqual(rule.dest_addr, "$EXTERNAL_NET")
+        self.assertEqual(rule.dest_port, "$HTTP_PORTS")
         self.assertEqual(rule.sid, 2014929)
         self.assertEqual(rule.rev, 1)
         self.assertEqual(rule.msg, "ET CURRENT_EVENTS Request to .in FakeAV Campaign June 19 2012 exe or zip")
@@ -48,35 +53,35 @@ class RuleTestCase(unittest.TestCase):
         self.assertEqual(rule.metadata[0], "stage")
         self.assertEqual(rule.metadata[1], "hostile_download")
         self.assertEqual(len(rule.flowbits), 2)
-        self.assertEquals(rule.flowbits[0], "isset,somebit")
-        self.assertEquals(rule.flowbits[1], "unset,otherbit")
-        self.assertEquals(rule.classtype, "trojan-activity")
+        self.assertEqual(rule.flowbits[0], "isset,somebit")
+        self.assertEqual(rule.flowbits[1], "unset,otherbit")
+        self.assertEqual(rule.classtype, "trojan-activity")
 
     def test_disable_rule(self):
         rule_buf = """# alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)"""
         rule = idstools.rule.parse(rule_buf)
         self.assertFalse(rule.enabled)
-        self.assertEquals(rule.raw, """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)""")
-        self.assertEquals(str(rule), rule_buf)
+        self.assertEqual(rule.raw, """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)""")
+        self.assertEqual(str(rule), rule_buf)
 
     def test_parse_rule_double_commented(self):
         rule_buf = """## alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)"""
         rule = idstools.rule.parse(rule_buf)
         self.assertFalse(rule.enabled)
-        self.assertEquals(rule.raw, """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)""")
+        self.assertEqual(rule.raw, """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)""")
 
     def test_parse_rule_comments_and_spaces(self):
         rule_buf = """## #alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)"""
         rule = idstools.rule.parse(rule_buf)
         self.assertFalse(rule.enabled)
-        self.assertEquals(rule.raw, """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)""")
+        self.assertEqual(rule.raw, """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)""")
 
     def test_toggle_rule(self):
         rule_buf = """# alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)"""
         rule = idstools.rule.parse(rule_buf)
         self.assertFalse(rule.enabled)
         rule.enabled = True
-        self.assertEquals(str(rule), """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)""")
+        self.assertEqual(str(rule), """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"some message";)""")
 
     def test_parse_fileobj(self):
         rule_buf = ("""# alert tcp $HOME_NET any -> $EXTERNAL_NET any """
@@ -86,7 +91,7 @@ class RuleTestCase(unittest.TestCase):
             fileobj.write(u"%s\n" % rule_buf)
         fileobj.seek(0)
         rules = idstools.rule.parse_fileobj(fileobj)
-        self.assertEquals(2, len(rules))
+        self.assertEqual(2, len(rules))
 
     def test_parse_file(self):
         rule_buf = ("""# alert tcp $HOME_NET any -> $EXTERNAL_NET any """
@@ -96,7 +101,7 @@ class RuleTestCase(unittest.TestCase):
             tmp.write(("%s\n" % rule_buf).encode())
         tmp.flush()
         rules = idstools.rule.parse_file(tmp.name)
-        self.assertEquals(2, len(rules))
+        self.assertEqual(2, len(rules))
 
     def test_parse_file_with_unicode(self):
         rules = idstools.rule.parse_file("./tests/rule-with-unicode.rules")
@@ -104,7 +109,7 @@ class RuleTestCase(unittest.TestCase):
     def test_parse_decoder_rule(self):
         rule_string = """alert ( msg:"DECODE_NOT_IPV4_DGRAM"; sid:1; gid:116; rev:1; metadata:rule-type decode; classtype:protocol-command-decode;)"""
         rule = idstools.rule.parse(rule_string)
-        self.assertEquals(rule["direction"], None)
+        self.assertEqual(rule["direction"], None)
 
     def test_multiline_rule(self):
         rule_string = u"""
@@ -112,30 +117,30 @@ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Request flood detected"; \
       app-layer-event:dnp3.flooded; sid:2200104; rev:1;)
 """
         rules = idstools.rule.parse_fileobj(io.StringIO(rule_string))
-        self.assertEquals(len(rules), 1)
+        self.assertEqual(len(rules), 1)
 
     def test_parse_nomsg(self):
         rule_string = u"""alert ip any any -> any any (content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:10000000; rev:1;)"""
         rule = idstools.rule.parse(rule_string)
-        self.assertEquals("", rule["msg"])
+        self.assertEqual("", rule["msg"])
 
     def test_add_option(self):
         rule_string = u"""alert ip any any -> any any (content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:10000000; rev:1;)"""
         rule = idstools.rule.parse(rule_string, "local.rules")
         rule = idstools.rule.add_option(
             rule, "msg", "\"This is a test description.\"", 0)
-        self.assertEquals("This is a test description.", rule["msg"])
-        self.assertEquals("local.rules", rule["group"])
+        self.assertEqual("This is a test description.", rule["msg"])
+        self.assertEqual("local.rules", rule["group"])
 
     def test_remove_option(self):
         rule_string = u"""alert ip any any -> any any (msg:"TEST MESSAGE"; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:10000000; rev:1;)"""
         rule = idstools.rule.parse(rule_string, "local.rules")
 
         rule = idstools.rule.remove_option(rule, "msg")
-        self.assertEquals("", rule["msg"])
+        self.assertEqual("", rule["msg"])
 
         rule = idstools.rule.remove_option(rule, "classtype")
-        self.assertEquals(None, rule["classtype"])
+        self.assertEqual(None, rule["classtype"])
 
     def test_remove_tag_option(self):
         rule_string = u"""alert ip any any -> any any (msg:"TEST RULE"; content:"uid=0|28|root|29|"; tag:session,5,packets; classtype:bad-unknown; sid:10000000; rev:1;)"""
@@ -146,7 +151,7 @@ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Request flood detected"; \
     def test_scratch(self):
         rule_string = """alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS (msg:"ET CURRENT_EVENTS Request to .in FakeAV Campaign June 19 2012 exe or zip"; flow:established,to_server; content:"setup."; fast_pattern:only; http_uri; content:".in|0d 0a|"; flowbits:isset,somebit; flowbits:unset,otherbit; http_header; pcre:"/\/[a-f0-9]{16}\/([a-z0-9]{1,3}\/)?setup\.(exe|zip)$/U"; pcre:"/^Host\x3a\s.+\.in\r?$/Hmi"; metadata:stage,hostile_download; reference:url,isc.sans.edu/diary/+Vulnerabilityqueerprocessbrittleness/13501; classtype:trojan-activity; sid:2014929; rev:1;)"""
         rule = idstools.rule.parse(rule_string)
-        self.assertEquals(rule_string, str(rule))
+        self.assertEqual(rule_string, str(rule))
 
         options = []
         for option in rule["options"]:
@@ -161,13 +166,13 @@ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Request flood detected"; \
         print("%s" % rule_string)
         print("%s" % reassembled)
 
-        self.assertEquals(rule_string, reassembled)
+        self.assertEqual(rule_string, reassembled)
         
     def test_parse_message_with_semicolon(self):
         rule_string = u"""alert ip any any -> any any (msg:"TEST RULE\; and some"; content:"uid=0|28|root|29|"; tag:session,5,packets; classtype:bad-unknown; sid:10000000; rev:1;)"""
         rule = idstools.rule.parse(rule_string)
         self.assertIsNotNone(rule)
-        self.assertEquals(rule.msg, "TEST RULE\; and some")
+        self.assertEqual(rule.msg, "TEST RULE\; and some")
 
         # Look for the expected content.
         found=False
@@ -181,7 +186,7 @@ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Request flood detected"; \
         rule_string = u"""alert tcp 93.174.88.0/21 any -> $HOME_NET any (msg:"SN: Inbound TCP traffic from suspect network (AS29073 - NL)"; flags:S; reference:url,https://suspect-networks.io/networks/cidr/13/; threshold: type limit, track by_dst, seconds 30, count 1; classtype:misc-attack; sid:71918985; rev:1;)"""
         rule = idstools.rule.parse(rule_string)
         self.assertIsNotNone(rule)
-        self.assertEquals(
+        self.assertEqual(
             rule.msg,
             "SN: Inbound TCP traffic from suspect network (AS29073 - NL)")
 
diff --git a/tests/test_rulecat.py b/tests/test_rulecat.py
index ad06270..023529d 100644
--- a/tests/test_rulecat.py
+++ b/tests/test_rulecat.py
@@ -64,11 +64,13 @@ class TestRulecat(unittest.TestCase):
 
     def test_run(self):
         old_path = os.getcwd()
+        if os.path.exists("./tmp"):
+            shutil.rmtree("tmp")
+        os.makedirs("./tmp")
+        stdout = open("./tmp/stdout", "wb")
+        stderr = open("./tmp/stderr", "wb")
         try:
             os.chdir(os.path.dirname(os.path.realpath(__file__)))
-            if os.path.exists("./tmp"):
-                shutil.rmtree("tmp")
-            os.makedirs("./tmp")
             subprocess.check_call(
                 [sys.executable,
                  "../bin/idstools-rulecat",
@@ -84,20 +86,24 @@ class TestRulecat(unittest.TestCase):
                  "--sid-msg-map", "./tmp/sid-msg.map",
                  "--sid-msg-map-2", "./tmp/sid-msg-v2.map",
                 ],
-                stdout=open("./tmp/stdout", "wb"),
-                stderr=open("./tmp/stderr", "wb"),
+                stdout=stdout,
+                stderr=stderr,
             )
             shutil.rmtree("tmp")
         except:
             if os.path.exists("./tmp/stdout"):
                 print("STDOUT")
-                print(open("./tmp/stdout").read())
+                with open("./tmp/stdout") as stdout:
+                    print(stdout.read())
             if os.path.exists("./tmp/stderr"):
                 print("STDERR")
-                print(open("./tmp/stderr").read())
+                with open("./tmp/stderr") as stderr:
+                    print(stderr.read())
             raise
         finally:
             os.chdir(old_path)
+            stderr.close()
+            stdout.close()
 
 class TestFetch(unittest.TestCase):
 
@@ -121,21 +127,21 @@ class ThresholdProcessorTestCase(unittest.TestCase):
         processor = rulecat.ThresholdProcessor()
 
         line = "suppress re:java"
-        self.assertEquals("java", processor.extract_regex(line))
+        self.assertEqual("java", processor.extract_regex(line))
         
         line = 'suppress re:"vulnerable java version"'
-        self.assertEquals(
+        self.assertEqual(
             "vulnerable java version", processor.extract_regex(line))
 
         line = "suppress re:java, track <by_src|by_dst>, ip <ip|subnet>"
-        self.assertEquals("java", processor.extract_regex(line))
+        self.assertEqual("java", processor.extract_regex(line))
     
         line = 'suppress re:"vulnerable java version", track <by_src|by_dst>, ip <ip|subnet>'
-        self.assertEquals(
+        self.assertEqual(
             "vulnerable java version", processor.extract_regex(line))
 
         line = 'threshold re:"vulnerable java version", type threshold, track by_dst, count 1, seconds 10'
-        self.assertEquals(
+        self.assertEqual(
             "vulnerable java version", processor.extract_regex(line))
 
     def test_replace(self):
@@ -143,17 +149,17 @@ class ThresholdProcessorTestCase(unittest.TestCase):
         rule = idstools.rule.parse(rule_string)
 
         line = "suppress re:windows"
-        self.assertEquals(
+        self.assertEqual(
             "suppress gen_id 1, sig_id 2020757",
             self.processor.replace(line, rule))
 
         line = 'threshold re:"ET MALWARE Windows", type threshold, ' \
                'track by_dst, count 1, seconds 10'
-        self.assertEquals("threshold gen_id 1, sig_id 2020757, type threshold, track by_dst, count 1, seconds 10", self.processor.replace(line, rule))
+        self.assertEqual("threshold gen_id 1, sig_id 2020757, type threshold, track by_dst, count 1, seconds 10", self.processor.replace(line, rule))
 
         line = 'threshold re:malware, type threshold, track by_dst, count 1, ' \
                'seconds 10'
-        self.assertEquals(
+        self.assertEqual(
             "threshold gen_id 1, sig_id 2020757, type threshold, "
             "track by_dst, count 1, seconds 10",
             self.processor.replace(line, rule))
@@ -236,7 +242,7 @@ class GroupMatcherTestCase(unittest.TestCase):
     def test_match(self):
         rule = idstools.rule.parse(self.rule_string, "rules/malware.rules")
         matcher = rulecat.parse_rule_match("group: */malware.rules")
-        self.assertEquals(
+        self.assertEqual(
             matcher.__class__, idstools.scripts.rulecat.GroupMatcher)
         self.assertTrue(matcher.match(rule))
 
@@ -247,7 +253,7 @@ class FilenameMatcherTestCase(unittest.TestCase):
     def test_match(self):
         rule = idstools.rule.parse(self.rule_string, "rules/trojan.rules")
         matcher = rulecat.parse_rule_match("trojan.rules")
-        self.assertEquals(
+        self.assertEqual(
             matcher.__class__, idstools.scripts.rulecat.FilenameMatcher)
         self.assertTrue(matcher.match(rule))
 
@@ -262,7 +268,7 @@ class DropRuleFilterTestCase(unittest.TestCase):
 
         drop_filter = rulecat.DropRuleFilter(id_matcher)
         rule1 = drop_filter.filter(rule0)
-        self.assertEquals("drop", rule1.action)
+        self.assertEqual("drop", rule1.action)
         self.assertTrue(rule1.enabled)
         self.assertTrue(str(rule1).startswith("drop"))
 
@@ -274,7 +280,7 @@ class DropRuleFilterTestCase(unittest.TestCase):
 
         drop_filter = rulecat.DropRuleFilter(id_matcher)
         rule1 = drop_filter.filter(rule0)
-        self.assertEquals("drop", rule1.action)
+        self.assertEqual("drop", rule1.action)
         self.assertFalse(rule1.enabled)
         self.assertTrue(str(rule1).startswith("# drop"))
         
diff --git a/tests/test_signaturemap.py b/tests/test_signaturemap.py
index e47412e..7940a24 100644
--- a/tests/test_signaturemap.py
+++ b/tests/test_signaturemap.py
@@ -7,59 +7,62 @@ class SignatureMapTestCase(unittest.TestCase):
     def test_load_generator_map(self):
 
         sigmap = maps.SignatureMap()
-        sigmap.load_generator_map(open("tests/gen-msg.map"))
+        with open("tests/gen-msg.map") as infile:
+            sigmap.load_generator_map(infile)
 
         sig = sigmap.get(1, 1)
         self.assertTrue(sig is not None)
-        self.assertEquals(1, sig["gid"])
-        self.assertEquals(1, sig["sid"])
-        self.assertEquals("snort general alert", sig["msg"])
+        self.assertEqual(1, sig["gid"])
+        self.assertEqual(1, sig["sid"])
+        self.assertEqual("snort general alert", sig["msg"])
 
         sig = sigmap.get(139, 1)
         self.assertTrue(sig is not None)
-        self.assertEquals(139, sig["gid"])
-        self.assertEquals(1, sig["sid"])
-        self.assertEquals(
+        self.assertEqual(139, sig["gid"])
+        self.assertEqual(1, sig["sid"])
+        self.assertEqual(
             "sensitive_data: sensitive data global threshold exceeded",
             sig["msg"])
 
     def test_load_signature_map(self):
 
         sigmap = maps.SignatureMap()
-        sigmap.load_signature_map(open("tests/sid-msg.map"))
+        with open("tests/sid-msg.map") as infile:
+            sigmap.load_signature_map(infile)
 
         # Get a basic signature.
         sig = sigmap.get(1, 2000356)
         self.assertTrue(sig is not None)
-        self.assertEquals(1, sig["gid"])
-        self.assertEquals(2000356, sig["sid"])
-        self.assertEquals("ET POLICY IRC connection", sig["msg"])
-        self.assertEquals(len(sig["ref"]), 1)
-        self.assertEquals("url,doc.emergingthreats.net/2000356", sig["ref"][0])
+        self.assertEqual(1, sig["gid"])
+        self.assertEqual(2000356, sig["sid"])
+        self.assertEqual("ET POLICY IRC connection", sig["msg"])
+        self.assertEqual(len(sig["ref"]), 1)
+        self.assertEqual("url,doc.emergingthreats.net/2000356", sig["ref"][0])
 
         # Try again but with a gid of 3.
-        self.assertEquals(sig, sigmap.get(3, 2000356))
+        self.assertEqual(sig, sigmap.get(3, 2000356))
 
         # This signature has multiple refs.
         sig = sigmap.get(1, 2000373)
-        self.assertEquals(3, len(sig["ref"]))
+        self.assertEqual(3, len(sig["ref"]))
 
         sig = sigmap.get(1, 71918985)
-        self.assertEquals(
+        self.assertEqual(
             "SN: Inbound TCP traffic from suspect network (AS29073 - NL)",
             sig["msg"])
 
     def test_load_signature_v2_map(self):
 
         sigmap = maps.SignatureMap()
-        sigmap.load_signature_map(open("tests/sid-msg-v2.map"))
+        with open("tests/sid-msg-v2.map") as infile:
+            sigmap.load_signature_map(infile)
 
         sig = sigmap.get(1, 2495)
-        self.assertEquals(1, sig["gid"])
-        self.assertEquals(2495, sig["sid"])
-        self.assertEquals("misc-attack", sig["classification"])
-        self.assertEquals(0, sig["priority"])
-        self.assertEquals(
+        self.assertEqual(1, sig["gid"])
+        self.assertEqual(2495, sig["sid"])
+        self.assertEqual("misc-attack", sig["classification"])
+        self.assertEqual(0, sig["priority"])
+        self.assertEqual(
             "GPL NETBIOS SMB DCEPRC ORPCThis request flood attempt",
             sig["msg"])
-        self.assertEquals(4, len(sig["ref"]))
+        self.assertEqual(4, len(sig["ref"]))
diff --git a/tests/test_snort.py b/tests/test_snort.py
index 983c4e7..fa73823 100644
--- a/tests/test_snort.py
+++ b/tests/test_snort.py
@@ -43,6 +43,6 @@ class SnortAppTestCase(unittest.TestCase):
         "file not installed")
     def test_guess_dynamic_engine_lib(self):
         app = snort.SnortApp(path = "/opt/nsm/bin/snort")
-        self.assertEquals(app.dynamic_engine_lib,
+        self.assertEqual(app.dynamic_engine_lib,
                           "/opt/nsm/lib/snort_dynamicengine/libsf_engine.so")
 
diff --git a/tests/test_unified2.py b/tests/test_unified2.py
index 78a9ab6..75833b4 100644
--- a/tests/test_unified2.py
+++ b/tests/test_unified2.py
@@ -57,7 +57,8 @@ class TestReadRecord(unittest.TestCase):
     def test_growing_file(self):
 
         write_fileobj = open("%s/unified2.log" % self.tmpdir, "ab")
-        write_fileobj.write(open(self.test_filename, "rb").read())
+        with open(self.test_filename, "rb") as test_file:
+            write_fileobj.write(test_file.read())
         write_fileobj.flush()
         write_fileobj.close()
 
@@ -70,7 +71,8 @@ class TestReadRecord(unittest.TestCase):
 
         # Grow the file by 17 more records.
         write_fileobj = open("%s/unified2.log" % self.tmpdir, "ab")
-        write_fileobj.write(open(self.test_filename, "rb").read())
+        with open(self.test_filename, "rb") as test_file:
+            write_fileobj.write(test_file.read())
         write_fileobj.flush()
         write_fileobj.close()
 
@@ -110,26 +112,28 @@ class TestRecordReader(unittest.TestCase):
     def test_short_read_of_header(self):
 
         # Just read in 6 bytes of the header.
-        buf = open(self.test_filename, "rb").read(6)
-        self.assertEquals(len(buf), 6)
+        with open(self.test_filename, "rb") as f:
+            buf = f.read(6)
+        self.assertEqual(len(buf), 6)
 
         fileobj = io.BytesIO(buf)
-        self.assertEquals(fileobj.tell(), 0)
+        self.assertEqual(fileobj.tell(), 0)
         reader = unified2.RecordReader(fileobj)
         self.assertRaises(EOFError, reader.next)
-        self.assertEquals(fileobj.tell(), 0)
+        self.assertEqual(fileobj.tell(), 0)
 
     def test_short_read_of_body(self):
 
         # Just read in 12, 8 for the header and some body.
-        buf = open(self.test_filename, "rb").read(12)
-        self.assertEquals(len(buf), 12)
+        with open(self.test_filename, "rb") as f:
+            buf = f.read(12)
+        self.assertEqual(len(buf), 12)
 
         fileobj = io.BytesIO(buf)
-        self.assertEquals(fileobj.tell(), 0)
+        self.assertEqual(fileobj.tell(), 0)
         reader = unified2.RecordReader(fileobj)
         self.assertRaises(EOFError, reader.next)
-        self.assertEquals(fileobj.tell(), 0)
+        self.assertEqual(fileobj.tell(), 0)
 
     def test_eof(self):
         """Test that we get None on EOF."""
@@ -145,7 +149,7 @@ class TestRecordReader(unittest.TestCase):
         # Should get 17 records.
         reader = unified2.RecordReader(open(self.test_filename, "rb"))
         records = [r for r in reader]
-        self.assertEquals(len(records), 17)
+        self.assertEqual(len(records), 17)
 
 class FileRecordReaderTest(unittest.TestCase):
 
@@ -161,20 +165,21 @@ class FileRecordReaderTest(unittest.TestCase):
     def test_single_file_iteration(self):
         reader = unified2.FileRecordReader(self.test_filename)
         records = list(reader)
-        self.assertEquals(17, len(records))
-        self.assertEquals(None, reader.next())
+        self.assertEqual(17, len(records))
+        self.assertEqual(None, reader.next())
 
     def test_multi_file_iteration(self):
         reader = unified2.FileRecordReader(
             self.test_filename, self.test_filename)
         records = list(reader)
-        self.assertEquals(34, len(records))
-        self.assertEquals(None, reader.next())
+        self.assertEqual(34, len(records))
+        self.assertEqual(None, reader.next())
 
     def test_growing_file(self):
 
         log_file = open("%s/unified2.log.0001" % (self.tmpdir), "ab")
-        log_file.write(open(self.test_filename, "rb").read())
+        with open(self.test_filename, "rb") as test_file:
+            log_file.write(test_file.read())
         log_file.close()
 
         reader = unified2.FileRecordReader("%s/unified2.log.0001" % self.tmpdir)
@@ -184,7 +189,8 @@ class FileRecordReaderTest(unittest.TestCase):
         self.assertTrue(reader.next() is None)
 
         log_file = open("%s/unified2.log.0001" % (self.tmpdir), "ab")
-        log_file.write(open(self.test_filename, "rb").read())
+        with open(self.test_filename, "rb") as test_file:
+            log_file.write(test_file.read())
         log_file.close()
 
         for i in range(17):
@@ -208,7 +214,8 @@ class AggregatorTestCase(unittest.TestCase):
             self.assertEqual(None, event)
 
         # On the first add of the next event we should get an event.
-        reader = unified2.RecordReader(open(self.test_filename, "rb"))
+        inner_file = open(self.test_filename, "rb")
+        reader = unified2.RecordReader(inner_file)
         event = aggregator.add(reader.next())
         self.assertTrue(event)
         self.assertTrue(isinstance(event, unified2.Event))
@@ -223,6 +230,9 @@ class AggregatorTestCase(unittest.TestCase):
         self.assertTrue(event)
         self.assertTrue(isinstance(event, unified2.Event))
 
+        # Cleanup.
+        inner_file.close()
+
     def test_interleaved(self):
 
         # First read in all records from a known multi-record event.
@@ -230,7 +240,7 @@ class AggregatorTestCase(unittest.TestCase):
         reader = unified2.RecordReader(open(self.test_filename, "rb"))
         for record in reader:
             records.append(record)
-        self.assertEquals(len(records), 17)
+        self.assertEqual(len(records), 17)
 
         # Modify the event ID of the last record.
         records[16]["event-id"] = records[16]["event-id"] - 1
@@ -241,7 +251,7 @@ class AggregatorTestCase(unittest.TestCase):
         aggregator = unified2.Aggregator()
         for record in records:
             aggregator.add(record)
-        self.assertEquals(len(aggregator.queue), 16)
+        self.assertEqual(len(aggregator.queue), 16)
 
 class FileEventReaderTestCase(unittest.TestCase):
 
@@ -267,7 +277,7 @@ class FileEventReaderTestCase(unittest.TestCase):
         """ Iteration test. """
         reader = unified2.FileEventReader(
             self.test_filename, self.test_filename)
-        self.assertEquals(len(list(reader)), 2)
+        self.assertEqual(len(list(reader)), 2)
 
 class SpoolRecordReaderTestCase(unittest.TestCase):
 
@@ -296,7 +306,7 @@ class SpoolRecordReaderTestCase(unittest.TestCase):
     def test_open_next(self):
 
         reader = unified2.SpoolRecordReader(self.tmpdir, "unified2")
-        self.assertEquals(None, reader.open_next())
+        self.assertEqual(None, reader.open_next())
 
         shutil.copy("tests/merged.log", "%s/unified2.log.0001" % (self.tmpdir))
         shutil.copy("tests/merged.log", "%s/unified2.log.0002" % (self.tmpdir))
@@ -304,16 +314,16 @@ class SpoolRecordReaderTestCase(unittest.TestCase):
         shutil.copy("tests/merged.log", "%s/unified2.log.0004" % (self.tmpdir))
 
         next_filename = reader.open_next()
-        self.assertEquals("unified2.log.0001", next_filename)
+        self.assertEqual("unified2.log.0001", next_filename)
         next_filename = reader.open_next()
-        self.assertEquals("unified2.log.0002", next_filename)
+        self.assertEqual("unified2.log.0002", next_filename)
         next_filename = reader.open_next()
-        self.assertEquals("unified2.log.0003", next_filename)
+        self.assertEqual("unified2.log.0003", next_filename)
         next_filename = reader.open_next()
-        self.assertEquals("unified2.log.0004", next_filename)
+        self.assertEqual("unified2.log.0004", next_filename)
 
         next_filename = reader.open_next()
-        self.assertEquals(None, next_filename)
+        self.assertEqual(None, next_filename)
         self.assertTrue(reader.fileobj is not None)
 
     def test_next(self):
@@ -349,16 +359,19 @@ class SpoolRecordReaderTestCase(unittest.TestCase):
 
     def test_next_with_unpexted_eof(self):
 
-        buf = io.BytesIO(open("tests/multi-record-event.log", "rb").read())
+        with open("tests/multi-record-event.log", "rb") as infile:
+            buf = io.BytesIO(infile.read())
 
         reader = unified2.SpoolRecordReader(self.tmpdir, "unified2")
 
         # Write out a couple bytes and try to read.
-        open("%s/unified2.log.0001" % self.tmpdir, "wb").write(buf.read(6))
+        with open("%s/unified2.log.0001" % self.tmpdir, "wb") as outfile:
+            outfile.write(buf.read(6))
         assert reader.next() is None
 
         # Write out the rest of file.
-        open("%s/unified2.log.0001" % self.tmpdir, "ab").write(buf.read())
+        with open("%s/unified2.log.0001" % self.tmpdir, "ab") as outfile:
+            outfile.write(buf.read())
         assert reader.next() is not None
 
     def test_with_growing_file(self):
@@ -366,14 +379,16 @@ class SpoolRecordReaderTestCase(unittest.TestCase):
         reader = unified2.SpoolRecordReader(self.tmpdir, "unified2")
 
         log_file = open("%s/unified2.log.0001" % (self.tmpdir), "ab")
-        log_file.write(open(self.test_filename, "rb").read())
+        with open(self.test_filename, "rb") as test_file:
+            log_file.write(test_file.read())
         log_file.close()
         for i in range(17):
             self.assertTrue(reader.next() is not None)
         self.assertTrue(reader.next() is None)
 
         log_file = open("%s/unified2.log.0001" % (self.tmpdir), "ab")
-        log_file.write(open(self.test_filename, "rb").read())
+        with open(self.test_filename, "rb") as test_file:
+            log_file.write(test_file.read())
         log_file.close()
         for i in range(17):
             self.assertTrue(reader.next() is not None)
@@ -385,7 +400,7 @@ class SpoolRecordReaderTestCase(unittest.TestCase):
 
         reader = unified2.SpoolRecordReader(self.tmpdir, "unified2")
         shutil.copy(test_filename, "%s/unified2.log.1382627902" % self.tmpdir)
-        self.assertEquals(len(list(reader)), 17)
+        self.assertEqual(len(list(reader)), 17)
 
     def test_open_at_bookmark(self):
 
@@ -394,17 +409,19 @@ class SpoolRecordReaderTestCase(unittest.TestCase):
         shutil.copy(self.test_filename, "%s/unified2.log.0002" % (self.tmpdir))
 
         # Make the 3rd one a concatenation of itself so we know a valid offset.
-        open("%s/unified2.log.0003" % self.tmpdir, "ab").write(
-            open(self.test_filename, "rb").read())
-        open("%s/unified2.log.0003" % self.tmpdir, "ab").write(
-            open(self.test_filename, "rb").read())
+        with open("%s/unified2.log.0003" % self.tmpdir, "ab") as out:
+            with open(self.test_filename, "rb") as infile:
+                out.write(infile.read())
+        with open("%s/unified2.log.0003" % self.tmpdir, "ab") as out:
+            with open(self.test_filename, "rb") as infile:
+                out.write(infile.read())
 
         # Now create the reader with a bookmark .
         reader = unified2.SpoolRecordReader(
             self.tmpdir, "unified2.log", "unified2.log.0003", 38950)
 
         # Now we should only read 17 records.
-        self.assertEquals(len(list(reader)), 17)
+        self.assertEqual(len(list(reader)), 17)
 
 class SpoolEventReaderTestCase(unittest.TestCase):
 
@@ -433,13 +450,15 @@ class SpoolEventReaderTestCase(unittest.TestCase):
         reader = unified2.SpoolEventReader(self.tmpdir, "unified2")
 
         log_file = open("%s/unified2.log.0001" % (self.tmpdir), "ab")
-        log_file.write(open(self.test_filename, "rb").read())
+        with open(self.test_filename, "rb") as test_file:
+            log_file.write(test_file.read())
         log_file.close()
         self.assertTrue(isinstance(reader.next(), unified2.Event))
         self.assertTrue(reader.next() is None)
 
         log_file = open("%s/unified2.log.0001" % (self.tmpdir), "ab")
-        log_file.write(open(self.test_filename, "rb").read())
+        with open(self.test_filename, "rb") as test_file:
+            log_file.write(test_file.read())
         log_file.close()
         self.assertTrue(isinstance(reader.next(), unified2.Event))
         self.assertTrue(reader.next() is None)
@@ -449,8 +468,9 @@ class SpoolEventReaderTestCase(unittest.TestCase):
         reader = unified2.SpoolEventReader(self.tmpdir, "unified2")
 
         for i in range(2):
-            open("%s/unified2.log.%04d" % (self.tmpdir, i), "ab").write(
-                open(self.test_filename, "rb").read())
+            with open("%s/unified2.log.%04d" % (self.tmpdir, i), "ab") as outfile:
+                with open(self.test_filename, "rb") as test_file:
+                    outfile.write(test_file.read())
             self.assertTrue(isinstance(reader.next(), unified2.Event))
 
         self.assertTrue(reader.next() is None)
@@ -462,8 +482,9 @@ class SpoolEventReaderTestCase(unittest.TestCase):
         """
 
         for i in range(2):
-            open("%s/unified2.log.%04d" % (self.tmpdir, i), "ab").write(
-                open(self.test_filename, "rb").read())
+            with open("%s/unified2.log.%04d" % (self.tmpdir, i), "ab") as outfile:
+                with open(self.test_filename, "rb") as test_file:
+                    outfile.write(test_file.read())
 
         reader = unified2.SpoolEventReader(
             self.tmpdir, "unified2", bookmark=True)
@@ -472,12 +493,12 @@ class SpoolEventReaderTestCase(unittest.TestCase):
         self.assertIsNotNone(event)
         print(reader.bookmark.get())
         bookmark_filename, bookmark_offset = reader.bookmark.get()
-        self.assertEquals(bookmark_filename, "unified2.log.0000")
+        self.assertEqual(bookmark_filename, "unified2.log.0000")
 
         # The offset should be the offset at end of the first event,
         # even though the offset of the underlying file has moved on.
-        self.assertEquals(bookmark_offset, 38950)
-        self.assertEquals(reader.reader.tell()[1], 68)
+        self.assertEqual(bookmark_offset, 38950)
+        self.assertEqual(reader.reader.tell()[1], 68)
 
         # Now create a new SpoolEventReader, the underlying offset
         # should be our bookmark locations.
@@ -486,24 +507,24 @@ class SpoolEventReaderTestCase(unittest.TestCase):
         bookmark_filename, bookmark_offset = reader.bookmark.get()
         underlying_filename, underlying_offset = reader.reader.tell()
 
-        self.assertEquals(bookmark_filename, "unified2.log.0000")
-        self.assertEquals(bookmark_offset, 38950)
+        self.assertEqual(bookmark_filename, "unified2.log.0000")
+        self.assertEqual(bookmark_offset, 38950)
 
-        self.assertEquals(
+        self.assertEqual(
             bookmark_filename, os.path.basename(underlying_filename))
-        self.assertEquals(bookmark_offset, underlying_offset)
+        self.assertEqual(bookmark_offset, underlying_offset)
 
         # Read the next and final event, and check bookmark location.
         self.assertIsNotNone(reader.next())
         self.assertIsNone(reader.next())
         bookmark_filename, bookmark_offset = reader.bookmark.get()
-        self.assertEquals(bookmark_filename, "unified2.log.0001")
-        self.assertEquals(bookmark_offset, 38950)
+        self.assertEqual(bookmark_filename, "unified2.log.0001")
+        self.assertEqual(bookmark_offset, 38950)
 
         # As this was the last event, the underlying location should
         # be the same as the bookmark.
         bookmark_filename, bookmark_offset = reader.bookmark.get()
         underlying_filename, underlying_offset = reader.reader.tell()
-        self.assertEquals(
+        self.assertEqual(
             bookmark_filename, os.path.basename(underlying_filename))
-        self.assertEquals(bookmark_offset, underlying_offset)
+        self.assertEqual(bookmark_offset, underlying_offset)
diff --git a/tests/test_util.py b/tests/test_util.py
index a0b86c8..bb07639 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -36,6 +36,6 @@ class Md5TestCase(unittest.TestCase):
         test_file = tempfile.NamedTemporaryFile()
         test_file.write(b"This is a test.")
         test_file.flush()
-        self.assertEquals(
+        self.assertEqual(
             "120ea8a25e5d487bf68b5f7096440019",
             util.md5_hexdigest(test_file.name))

pip install -e .
parse_log_pytest
requirements.txt
pytest pyyaml
apt-get update apt-get install -y gcc
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
attrs==22.2.0
Babel==2.11.0
certifi==2021.5.30
charset-normalizer==2.0.12
docutils==0.18.1
idna==3.10
-e git+https://github.com/jasonish/py-idstools.git@a97393d38f3a69fb34ca962918a33688f58c22f8#egg=idstools
imagesize==1.4.1
importlib-metadata==4.8.3
iniconfig==1.1.1
Jinja2==3.0.3
MarkupSafe==2.0.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
Pygments==2.14.0
pyparsing==3.1.4
pytest==7.0.1
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.1
requests==2.27.1
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-programoutput==0.17
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: py-idstools
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - attrs==22.2.0
      - babel==2.11.0
      - charset-normalizer==2.0.12
      - docutils==0.18.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jinja2==3.0.3
      - markupsafe==2.0.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.1
      - requests==2.27.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-programoutput==0.17
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/py-idstools

tests/test_rule.py::RuleTestCase::test_parse1
tests/test_classificationmap.py::ClassificationMapTestCase::test_load_from_file tests/test_packet.py::TestIPv6WithExtensionHeader::test_decode tests/test_rule.py::RuleTestCase::test_add_option tests/test_rule.py::RuleTestCase::test_disable_rule tests/test_rule.py::RuleTestCase::test_metadata_missing_semicolon tests/test_rule.py::RuleTestCase::test_multiline_rule tests/test_rule.py::RuleTestCase::test_parse tests/test_rule.py::RuleTestCase::test_parse_decoder_rule tests/test_rule.py::RuleTestCase::test_parse_file tests/test_rule.py::RuleTestCase::test_parse_file_with_unicode tests/test_rule.py::RuleTestCase::test_parse_fileobj tests/test_rule.py::RuleTestCase::test_parse_message_with_colon tests/test_rule.py::RuleTestCase::test_parse_message_with_semicolon tests/test_rule.py::RuleTestCase::test_parse_multiple_metadata tests/test_rule.py::RuleTestCase::test_parse_nomsg tests/test_rule.py::RuleTestCase::test_parse_rule_comments_and_spaces tests/test_rule.py::RuleTestCase::test_parse_rule_double_commented tests/test_rule.py::RuleTestCase::test_remove_option tests/test_rule.py::RuleTestCase::test_remove_tag_option tests/test_rule.py::RuleTestCase::test_scratch tests/test_rule.py::RuleTestCase::test_toggle_rule tests/test_rulecat.py::TestRulecat::test_extract_tar tests/test_rulecat.py::TestRulecat::test_extract_zip tests/test_rulecat.py::TestRulecat::test_run tests/test_rulecat.py::TestRulecat::test_try_extract tests/test_rulecat.py::TestFetch::test_check_checksum tests/test_rulecat.py::ThresholdProcessorTestCase::test_extract_regex tests/test_rulecat.py::ThresholdProcessorTestCase::test_replace tests/test_rulecat.py::ModifyRuleFilterTestCase::test_drop_to_alert tests/test_rulecat.py::ModifyRuleFilterTestCase::test_id_match tests/test_rulecat.py::ModifyRuleFilterTestCase::test_oinkmaster_backticks tests/test_rulecat.py::ModifyRuleFilterTestCase::test_oinkmaster_backticks_not_noalert tests/test_rulecat.py::ModifyRuleFilterTestCase::test_re_backref_one tests/test_rulecat.py::ModifyRuleFilterTestCase::test_re_backref_two tests/test_rulecat.py::ModifyRuleFilterTestCase::test_re_match tests/test_rulecat.py::GroupMatcherTestCase::test_match tests/test_rulecat.py::FilenameMatcherTestCase::test_match tests/test_rulecat.py::DropRuleFilterTestCase::test_disabled_rule tests/test_rulecat.py::DropRuleFilterTestCase::test_drop_noalert tests/test_rulecat.py::DropRuleFilterTestCase::test_enabled_rule tests/test_signaturemap.py::SignatureMapTestCase::test_load_generator_map tests/test_signaturemap.py::SignatureMapTestCase::test_load_signature_map tests/test_signaturemap.py::SignatureMapTestCase::test_load_signature_v2_map tests/test_unified2.py::TestReadRecord::test_decoders tests/test_unified2.py::TestReadRecord::test_growing_file tests/test_unified2.py::TestReadRecord::test_read_ipv6_event tests/test_unified2.py::TestRecordReader::test_eof tests/test_unified2.py::TestRecordReader::test_iteration tests/test_unified2.py::TestRecordReader::test_short_read_of_body tests/test_unified2.py::TestRecordReader::test_short_read_of_header tests/test_unified2.py::FileRecordReaderTest::test_growing_file tests/test_unified2.py::FileRecordReaderTest::test_multi_file_iteration tests/test_unified2.py::FileRecordReaderTest::test_single_file_iteration tests/test_unified2.py::AggregatorTestCase::test_aggregator tests/test_unified2.py::AggregatorTestCase::test_interleaved tests/test_unified2.py::FileEventReaderTestCase::test tests/test_unified2.py::FileEventReaderTestCase::test_iteration tests/test_unified2.py::SpoolRecordReaderTestCase::test_get_filenames tests/test_unified2.py::SpoolRecordReaderTestCase::test_iteration tests/test_unified2.py::SpoolRecordReaderTestCase::test_next tests/test_unified2.py::SpoolRecordReaderTestCase::test_next_with_unpexted_eof tests/test_unified2.py::SpoolRecordReaderTestCase::test_open_at_bookmark tests/test_unified2.py::SpoolRecordReaderTestCase::test_open_next tests/test_unified2.py::SpoolRecordReaderTestCase::test_with_growing_file tests/test_unified2.py::SpoolEventReaderTestCase::test_bookmarking tests/test_unified2.py::SpoolEventReaderTestCase::test_eof tests/test_unified2.py::SpoolEventReaderTestCase::test_with_file_rotation tests/test_unified2.py::SpoolEventReaderTestCase::test_with_growing_file tests/test_util.py::Md5TestCase::test_hexdigest
idstools/scripts/u2eve.py idstools/unified2.py idstools/rule.py idstools/scripts/u2fast.py idstools/scripts/rulecat.py idstools/scripts/u2json.py idstools/scripts/eve2pcap.py idstools/snort.py tox.ini
idstools/scripts/u2eve.py idstools/unified2.py idstools/rule.py idstools/scripts/u2fast.py idstools/scripts/rulecat.py idstools/scripts/u2json.py idstools/scripts/eve2pcap.py idstools/snort.py tox.ini