Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#942 add Path.toNotBeReadable #989

Merged
merged 1 commit into from
Oct 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ThrowableExpectationSamples {
}

fails { // because wrong type expected (IllegalStateException instead of IndexOutOfBoundsException), but since we use a block...
expect(IllegalStateException(IndexOutOfBoundsException("abc"))).cause<IllegalStateException> {
expect(IllegalStateException(IndexOutOfBoundsException("abc"))).cause<IllegalStateException> {
messageToContain("b") // ... reporting mentions that subject's message was expected `to contain: "b"`
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,28 @@ fun <T : Path> Expect<T>.notToExist(): Expect<T> =
fun <T : Path> Expect<T>.toBeReadable(): Expect<T> =
_logicAppend { isReadable() }

/**
botex98 marked this conversation as resolved.
Show resolved Hide resolved
* Expects that the subject of `this` expectation (a [Path]) is not readable;
* meaning that there is a file system entry at the location the [Path] points to and
* that the current thread does not have the permission to read from it.
*
* This assertion _resolves_ symbolic links.
* Therefore, if a symbolic link exists at the location the subject points to,
* search will continue at the location the link points at.
*
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*
* @return an [Expect] for the subject of `this` expectation.
*
botex98 marked this conversation as resolved.
Show resolved Hide resolved
* @sample ch.tutteli.atrium.api.fluent.en_GB.samples.PathExpectationSamples.notToBeReadable
*
* @since 0.17.0
*/
fun <T : Path> Expect<T>.notToBeReadable(): Expect<T> =
_logicAppend { isNotReadable() }

/**
* Expects that the subject of `this` expectation (a [Path]) is writable;
* meaning that there is a file system entry at the location the [Path] points to and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
fun1(Expect<Path>::toEndWith),
fun1(Expect<Path>::notToEndWith),
fun0(Expect<Path>::toBeReadable),
fun0(Expect<Path>::notToBeReadable),
fun0(Expect<Path>::toBeWritable),
fun0(Expect<Path>::notToBeWritable),
fun0(Expect<Path>::toBeExecutable),
Expand Down Expand Up @@ -58,6 +59,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
a1.toEndWith(Paths.get("a"))
a1.notToEndWith(Paths.get("a"))
a1.toBeReadable()
a1.notToBeReadable()
a1.toBeWritable()
a1.notToBeWritable()
a1.toBeExecutable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,39 @@ class PathExpectationSamples {
}
}

@Test
fun notToBeReadable() {
assertIf(ifPosixSupported) {
val writeOnlyPermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("-w--w--w-"))
val readyWritePermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-rw-rw-"))

val writeOnlyDir = tempDir.newDirectory("write_only_dir", writeOnlyPermissions)
val writeOnlyFile = tempDir.newFile("write_only_file", writeOnlyPermissions)
val readWriteDir = tempDir.newDirectory("read_write_dir", readyWritePermissions)
val readWriteFile = tempDir.newFile("read_write_file", readyWritePermissions)

expect(writeOnlyDir).notToBeReadable()
expect(writeOnlyFile).notToBeReadable()

fails {
expect(readWriteDir).notToBeReadable()
}
fails {
expect(readWriteFile).notToBeReadable()
}

fails {
expect(readWriteDir).notToBeReadable()
}
}

fails {
expect(Paths.get("non_existing_dir")).notToBeReadable()
}
}

@Test
fun toBeWritable() {
val dir = tempDir.newDirectory("test_dir")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ fun <E> path(path: String, assertionCreator: Expect<E>.() -> Unit): PathWithCrea
infix fun <T : Path> Expect<T>.toBe(@Suppress("UNUSED_PARAMETER") readable: readable): Expect<T> =
_logicAppend { isReadable() }


//TODO move to pathExpectations.kt with 0.18.0
/**
* Expects that the subject of `this` expectation (a [Path]) is writable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,28 @@ infix fun <T : Path> Expect<T>.toHaveTheSameTextualContentAs(pathWithEncoding: P
infix fun <T : Path> Expect<T>.toHaveTheSameBinaryContentAs(targetPath: Path): Expect<T> =
_logicAppend { hasSameBinaryContentAs(targetPath) }

/**
* Expects that the subject of `this` expectation (a [Path]) is not readable;
* meaning that there is a file system entry at the location the [Path] points to and
* that the current thread does not have the permission to read from it.
*
* This assertion _resolves_ symbolic links.
* Therefore, if a symbolic link exists at the location the subject points to,
* search will continue at the location the link points at.
*
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*
* @return an [Expect] for the subject of `this` expectation.
*
* @sample ch.tutteli.atrium.api.infix.en_GB.samples.PathExpectationSamples.notToBeReadable
*
* @since 0.17.0
*/
infix fun <T : Path> Expect<T>.notToBe(@Suppress("UNUSED_PARAMETER") readable: readable): Expect<T> =
_logicAppend { isNotReadable() }

/**
* Expects that the subject of `this` expectation (a [Path]) is not writable;
* meaning that there is a file system entry at the location the [Path] points to and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
fun1(Expect<Path>::toEndWith),
fun1(Expect<Path>::notToEndWith),
"toBe ${readable::class.simpleName}" to Companion::toBeReadable,
"notToBe ${readable::class.simpleName}" to Companion::notToBeReadable,
"toBe ${writable::class.simpleName}" to Companion::toBeWritable,
"notToBe ${writable::class.simpleName}" to Companion::notToBeWritable,
"toBe ${executable::class.simpleName}" to Companion::toBeExecutable,
Expand Down Expand Up @@ -45,6 +46,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
private fun toExist(expect: Expect<Path>) = expect toBe existing
private fun notToExist(expect: Expect<Path>) = expect notToBe existing
private fun toBeReadable(expect: Expect<Path>) = expect toBe readable
private fun notToBeReadable(expect: Expect<Path>) = expect notToBe readable
private fun toBeWritable(expect: Expect<Path>) = expect toBe writable
private fun notToBeWritable(expect: Expect<Path>) = expect notToBe writable
private fun toBeExecutable(expect: Expect<Path>) = expect toBe executable
Expand Down Expand Up @@ -91,6 +93,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
a1 toEndWith Paths.get("a")
a1 notToEndWith Paths.get("a")
a1 toBe readable
a1 notToBe readable
a1 toBe writable
a1 notToBe writable
a1 toBe executable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,35 @@ class PathExpectationSamples {
}
}

@Test
fun notToBeReadable() {
assertIf(fileSystemSupportsPosixPermissions()) {
val writeOnlyPermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("-w--w--w-"))
val readWritePermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-rw-rw-"))

val writeOnlyDir = tempDir.newDirectory("write_only_dir", writeOnlyPermissions)
val writeOnlyFile = tempDir.newFile("write_only_file", writeOnlyPermissions)
val readWriteDir = tempDir.newDirectory("read_write_dir", readWritePermissions)
val readWriteFile = tempDir.newFile("read_write_file", readWritePermissions)

expect(writeOnlyDir) notToBe readable
expect(writeOnlyFile) notToBe readable

fails {
expect(readWriteDir) notToBe readable
}
fails {
expect(readWriteFile) notToBe readable
}
}

fails {
expect(Paths.get("non_existing_dir")) notToBe readable
}
}

@Test
fun toBeWritable() {
val dir = tempDir.newDirectory("test_dir")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fun <T : Path> AssertionContainer<T>.exists(linkOption: LinkOption? = null): Ass
fun <T : Path> AssertionContainer<T>.existsNot(linkOption: LinkOption? = null): Assertion = impl.existsNot(this, linkOption)

fun <T : Path> AssertionContainer<T>.isReadable(): Assertion = impl.isReadable(this)
fun <T : Path> AssertionContainer<T>.isNotReadable(): Assertion = impl.isNotReadable(this)
fun <T : Path> AssertionContainer<T>.isWritable(): Assertion = impl.isWritable(this)
fun <T : Path> AssertionContainer<T>.isNotWritable(): Assertion = impl.isNotWritable(this)
fun <T : Path> AssertionContainer<T>.isExecutable(): Assertion = impl.isExecutable(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface PathAssertions {
fun <T : Path> existsNot(container: AssertionContainer<T>, linkOption: LinkOption? = null): Assertion

fun <T : Path> isReadable(container: AssertionContainer<T>): Assertion
fun <T : Path> isNotReadable(container: AssertionContainer<T>): Assertion
fun <T : Path> isWritable(container: AssertionContainer<T>): Assertion
fun <T : Path> isNotWritable(container: AssertionContainer<T>): Assertion
fun <T : Path> isExecutable(container: AssertionContainer<T>): Assertion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ class DefaultPathAssertions : PathAssertions {
override fun <T : Path> isReadable(container: AssertionContainer<T>): Assertion =
filePermissionAssertion(container, READABLE, AccessMode.READ, DescriptionBasic.IS, shouldHaveAccess = true)

override fun <T : Path> isNotReadable(container: AssertionContainer<T>): Assertion =
filePermissionAssertion(
container,
READABLE,
AccessMode.READ,
DescriptionBasic.IS_NOT,
shouldHaveAccess = false
)

override fun <T : Path> isWritable(container: AssertionContainer<T>): Assertion =
filePermissionAssertion(container, WRITABLE, AccessMode.WRITE, DescriptionBasic.IS, shouldHaveAccess = true)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ abstract class PathExpectationsSpec(
toEndWith: Fun1<Path, Path>,
notToEndWith: Fun1<Path, Path>,
toBeReadable: Fun0<Path>,
notToBeReadable: Fun0<Path>,
toBeWritable: Fun0<Path>,
notToBeWritable: Fun0<Path>,
toBeExecutable: Fun0<Path>,
Expand Down Expand Up @@ -71,6 +72,7 @@ abstract class PathExpectationsSpec(
toEndWith.forSubjectLess(Paths.get("a")),
notToEndWith.forSubjectLess(Paths.get("a")),
toBeReadable.forSubjectLess(),
notToBeReadable.forSubjectLess(),
toBeWritable.forSubjectLess(),
notToBeWritable.forSubjectLess(),
toBeExecutable.forSubjectLess(),
Expand Down Expand Up @@ -540,6 +542,115 @@ abstract class PathExpectationsSpec(
}
}

describeFun(notToBeReadable) {
val notToBeReadableFun = notToBeReadable.lambda
val expectedMessage = "$isNotDescr: ${READABLE.getDefault()}"

context("not accessible") {
it("throws an AssertionError for a non-existent path") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.tmpDir.resolve("nonExistent"))
expect {
expect(file).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(
expectedMessage,
FAILURE_DUE_TO_NO_SUCH_FILE.getDefault()
)
containsExplanationFor(maybeLink)
}
}

itPrintsFileAccessProblemDetails(
// because if we cannot access parent then it is still not readable
checkParentHints = false
) { testFile ->
expect(testFile).notToBeReadableFun()
}
}

context("POSIX: readable", skip = ifPosixNotSupported) {
it("throws an AssertionError for a file") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.newFile("readable"))

file.whileWithPermissions(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, OTHERS_EXECUTE) {
expect {
expect(file).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(expectedMessage)
}
}
}

it("throws an AssertionError for a directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("readable"))

folder.whileWithPermissions(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, OTHERS_EXECUTE) {
expect {
expect(folder).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(expectedMessage)
}
}
}
}

context("POSIX: not readable", skip = ifPosixNotSupported) {
it("does not throw for a file") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.newFile("not-readable"))
file.whileWithPermissions(OWNER_WRITE, OWNER_EXECUTE, OTHERS_EXECUTE) {
expect(file).notToBeReadableFun()
}
}

it("does not throw for a directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("not-readable"))
folder.whileWithPermissions(OWNER_WRITE, OWNER_EXECUTE, OTHERS_EXECUTE) {
expect(folder).notToBeReadableFun()
}
}
}

context("ACL: not writable", skip = ifAclNotSupported) {
it("does not throw for a file") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.newFile("not-readable"))
file.whileWithAcl(TestAcls::ownerNoRead) {
expect(file).notToBeReadableFun()
}
}

it("does not throw for a directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("not-readable"))
folder.whileWithAcl(TestAcls::ownerNoRead) {
expect(folder).notToBeReadableFun()
}
}
}

context("ACL: writable", skip = ifAclNotSupported) {
it("throws an AssertionError for a file") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.newFile("readable"))
file.whileWithAcl(TestAcls::all) {
expect {
expect(file).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(expectedMessage)
}
}
}

it("throws an AssertionError for a directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("readable"))
folder.whileWithAcl(TestAcls::all) {
expect {
expect(folder).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(expectedMessage)
}
}
}
}
}

describeFun(toBeWritable) {
val toBeWritableFun = toBeWritable.lambda
val expectedMessage = "$isDescr: ${WRITABLE.getDefault()}"
Expand Down