From cb03bc6d230f514b6084bd2a0d7af69a6f9d0b42 Mon Sep 17 00:00:00 2001 From: May Date: Sat, 1 Aug 2020 11:35:43 +0200 Subject: [PATCH] PoC for file validation #2 Implemented 3 simple examples for severe cases --- Sources/FITS/VERIFY/Condition.swift | 64 ++++++++++++ Sources/FITS/VERIFY/SevereConditions.swift | 76 ++++++++++++++ Tests/FITSTests/VerificationTests.swift | 109 +++++++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 Sources/FITS/VERIFY/Condition.swift create mode 100644 Sources/FITS/VERIFY/SevereConditions.swift create mode 100644 Tests/FITSTests/VerificationTests.swift diff --git a/Sources/FITS/VERIFY/Condition.swift b/Sources/FITS/VERIFY/Condition.swift new file mode 100644 index 0000000..b0cdf2e --- /dev/null +++ b/Sources/FITS/VERIFY/Condition.swift @@ -0,0 +1,64 @@ +/* + + Copyright (c) <2020> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +import Foundation + +enum Severiy { + case SEVERE + case ERROR + case WARNING +} + +/** + Programatically notation of a criterion to check for verification of a HDU + + - Parameter hdu: the Header Data Unit to verify + + - Returns: `true` if the condition holds (positive result) or `false` if the condition is not satisfied (negative result) + */ +typealias Criterion = (_ hdu: AnyHDU) -> Bool + +/// A condition to verfiy +protocol Condition { + + /// description of the condition + var description : String {get} + + /// severity of the condition + var severity : Severiy {get} + + /** + Implementation of the `Criterion` to check as a precondtion + + - SeeAlso: `Criterion` + */ + var precondition: Criterion {get} + + /** + Implementation of the `Criterion` to check if the precondtion is satisfied + + - SeeAlso: `Criterion` + */ + var check : Criterion {get} +} diff --git a/Sources/FITS/VERIFY/SevereConditions.swift b/Sources/FITS/VERIFY/SevereConditions.swift new file mode 100644 index 0000000..34791fe --- /dev/null +++ b/Sources/FITS/VERIFY/SevereConditions.swift @@ -0,0 +1,76 @@ +/* + + Copyright (c) <2020> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +struct SevereCondition : Condition { + + var description: String + let severity: Severiy = .SEVERE + + var precondition: Criterion + var check: Criterion +} + +/// END header keyword is not present +let noend : Condition = SevereCondition(description: "END header keyword is not present", precondition: +{ hdu in + return true +}) { hdu in + return hdu.headerUnit.contains(where: {$0.keyword == HDUKeyword.END}) +} + +/// Sum of table column widths is inconsistent with NAXIS1 value +let badnaxis1 : Condition = SevereCondition(description: "Sum of table column widths is inconsistent with NAXIS1 value", precondition: +{ hdu in + return hdu is TableHDU || hdu is BintableHDU +}) { hdu in + + if let table = hdu as? TableHDU { + return table.columns.reduce(into: 0) { sum, column in + sum += column.TFORM?.length ?? 0 + } == hdu.naxis(1) + } + if let table = hdu as? BintableHDU { + return table.columns.reduce(into: 0) { sum, column in + sum += column.TFORM?.length ?? 0 + } == hdu.naxis(1) + } + return false +} + +/// BLANK keyword present in image with floating-point datatype +let badblank : Condition = SevereCondition(description: "BLANK keyword present in image with floating-point datatype" , precondition: +{ hdu in + return hdu is AnyImageHDU && (hdu.bitpix == BITPIX.FLOAT32 || hdu.bitpix == BITPIX.FLOAT64) +}) { hdu in + return !hdu.headerUnit.contains(where: {$0.keyword == HDUKeyword.BLANK}) +} + +/// TNULLn keyword present for floating-point binary table column +let badtnull : Condition = SevereCondition(description: "TNULLn keyword present for floating-point binary table column") +{ hdu in + return hdu is BintableHDU && (hdu.bitpix == BITPIX.FLOAT32 || hdu.bitpix == BITPIX.FLOAT64) +} check: { hdu in + return !hdu.headerUnit.contains(where: {$0.keyword.starts(with: "TNULL")}) +} + diff --git a/Tests/FITSTests/VerificationTests.swift b/Tests/FITSTests/VerificationTests.swift new file mode 100644 index 0000000..6974998 --- /dev/null +++ b/Tests/FITSTests/VerificationTests.swift @@ -0,0 +1,109 @@ + +import XCTest +@testable import FITS + + +final class VerificationTests: XCTestCase { + + static var allTests = [ + ("testVerifyNoEnd", testVerifyNoEnd), + ] + + func XCTAssertCondition(_ condition: Condition, _ positive: AnyHDU,_ negative: AnyHDU, file: StaticString = #file, line: UInt = #line) { + + XCTAssertPositive(condition, positive) + XCTAssertNegative(condition, negative) + } + + func XCTAssertNegative(_ condition: Condition,_ negative: AnyHDU, file: StaticString = #file, line: UInt = #line) { + XCTAssertFalse(condition.check(negative), "False positive") + } + + func XCTAssertPositive(_ condition: Condition, _ positive: AnyHDU, file: StaticString = #file, line: UInt = #line) { + + XCTAssertTrue(condition.check(positive), "False negative") + } + + func XCTAssertApplicable(_ condition: Condition, _ hdu: AnyHDU, file: StaticString = #file, line: UInt = #line) { + XCTAssertTrue(condition.precondition(hdu), "Precondtion not satisfied") + } + + func XCTAssertInApplicable(_ condition: Condition, _ hdu: AnyHDU, file: StaticString = #file, line: UInt = #line) { + XCTAssertFalse(condition.precondition(hdu), "Precondtion surprisingly satisfied") + } + + + + func testVerifyNoEnd() { + + /// Negative case + let bad = PrimaryHDU() + XCTAssertNegative(noend, bad) + + /// Positive case + let good = PrimaryHDU() + good.header(HDUKeyword.END, comment: nil) + XCTAssertPositive(noend, good) + + /// Ignored cases do not exist + } + + func testBadNaxis1() { + + /// Negative case + let bad1 = TableHDU() + _ = bad1.addColumn(TFORM: TFORM.A(w: 4), TFIELD.A(val: "test")) + bad1.header("NAXIS1", value: 42, comment: nil) + XCTAssertApplicable(badnaxis1, bad1) + XCTAssertNegative(badnaxis1, bad1) + + /// Positive case + let good1 = TableHDU() + _ = good1.addColumn(TFORM: TFORM.A(w: 4), TFIELD.A(val: "test")) + XCTAssertApplicable(badnaxis1, good1) + XCTAssertPositive(badnaxis1, good1) + + /// Ignored cases do not exist + let irrelevant1 = PrimaryHDU() + XCTAssertInApplicable(badnaxis1, irrelevant1) + } + + func testBadBlank() { + + /// Negative case + let bad1 = Sample().rgb(FITSByte_F.self).prime + bad1.header(HDUKeyword.BLANK, comment: nil) + XCTAssertApplicable(badblank, bad1) + XCTAssertNegative(badblank, bad1) + + /// Positive case + let good1 = Sample().rgb(FITSByte_F.self).prime + XCTAssertApplicable(badblank, good1) + XCTAssertPositive(badblank, good1) + + /// Ignored cases + let irrelevant1 = Sample().rgb(FITSByte_8.self).prime + XCTAssertInApplicable(badblank, irrelevant1) + } + + func testBadTNull() { + + /// Negative case + let bad1 = BintableHDU() + bad1.bitpix = BITPIX.FLOAT32 + bad1.header("TNULL1", comment: nil) + XCTAssertApplicable(badtnull, bad1) + XCTAssertNegative(badtnull, bad1) + + /// Positive case + let good1 = BintableHDU() + good1.bitpix = BITPIX.FLOAT32 + XCTAssertApplicable(badtnull, good1) + XCTAssertPositive(badtnull, good1) + + /// Ignored cases + let irrelevant1 = BintableHDU() + bad1.bitpix = BITPIX.INT32 + XCTAssertInApplicable(badtnull, irrelevant1) + } +}