summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift
blob: 8bf34893f06e55caa9d839906ad5b4a5b0ab925e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//
//  LogFileOutputStreamTests.swift
//  MullvadVPNTests
//
//  Created by Marco Nikic on 2025-01-21.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

@preconcurrency import Foundation
import Testing

@testable import MullvadLogging

@Suite("LogFileOutputStream Tests")
actor LogFileOutputStreamTests {
    let fileManager = FileManager.default
    var directoryPath: URL!

    init() async throws {
        directoryPath = FileManager.default.temporaryDirectory.appendingPathComponent(
            UUID().uuidString,
            isDirectory: true
        )

        try fileManager.createDirectory(
            at: directoryPath,
            withIntermediateDirectories: true
        )
    }

    deinit {
        try? fileManager.removeItem(at: directoryPath)
    }

    @Test func logHeaderGetsWrittenAtFileStartAfterTruncation() async throws {
        let header = "header"
        let message = """
            old

            """
        let fileSizeLimit: UInt64 = 20
        let fileURL = directoryPath.appendingPathComponent(UUID().uuidString)
        let stream = LogFileOutputStream(
            fileURL: fileURL,
            header: header,
            fileSizeLimit: fileSizeLimit,
            newLineChunkReadSize: 3
        )
        // Fill the file with the word "old" to force truncation in half
        for _ in 0..<3 {
            stream.write(message)
        }
        /* At this point, the file contains the following string (of length 19)
         "header\nold\nold\nold"
                    ^
                    Half point of the file
        
         Writing the word "new" goes over the file size limit (20),
         so the file will get truncated to its half point.
         In order to keep a nice UX for reading log, the stream will move the internal file cursor to after the next "\n"
         character, and read the last half of the file in order to paste it at the beginning
         after truncation.
         In this example, the string "old\nold\n" will be buffered, which will then
         get prepended with "header\n"
          */
        stream.synchronize()
        stream.write("new")
        stream.synchronize()

        let fileContents = try #require(
            try String(contentsOf: fileURL, encoding: .utf8)
        )
        let expectedContents = """
            header
            old
            old
            new
            """

        #expect(fileContents == expectedContents)
    }

    @Test func fileSizeCounterGetsResetAfterTruncation() async throws {
        let header = "header"
        let message = """
            old

            """
        let fileSizeLimit: UInt64 = 20
        let fileURL = directoryPath.appendingPathComponent(UUID().uuidString)
        let stream = LogFileOutputStream(
            fileURL: fileURL,
            header: header,
            fileSizeLimit: fileSizeLimit
        )
        // Fill the file with the word "old" to force truncation in half
        for _ in 0..<3 {
            stream.write(message)
        }
        // File gets truncated in half here
        stream.write("new")
        stream.write("a")
        stream.synchronize()

        /// If the `partialFileSizeCounter` didn't get reset after truncating,
        /// a new write will truncate the file again instead of just appending
        let expectedContents = """
            header
            d
            old
            newa
            """
        let fileContents = try #require(
            try String(contentsOf: fileURL, encoding: .utf8)
        )
        #expect(fileContents == expectedContents)
    }
}