Skip to content

Commit

Permalink
#1015 Path.notToBeExecutable (#1016)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhushikesh authored Oct 25, 2021
1 parent fd00679 commit fc40766
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,28 @@ fun <T : Path> Expect<T>.notToBeWritable(): Expect<T> =
fun <T : Path> Expect<T>.toBeExecutable(): Expect<T> =
_logicAppend { isExecutable() }

/**
* Expects that the subject of `this` expectation (a [Path]) is not executable;
* 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 execute it.
*
* The semantics of “permission to execute it” may differ when checking access to a directory. For example, on UNIX
* systems, it means that the Java virtual machine has permission to search the directory in order to access file or
* subdirectories.
*
* 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.
*
* @return an [Expect] for the subject of `this` expectation.
*
* @sample ch.tutteli.atrium.api.fluent.en_GB.samples.PathExpectationSamples.notToBeExecutable
*
* @since 0.17.0
*/
fun <T : Path> Expect<T>.notToBeExecutable(): Expect<T> =
_logicAppend { isNotExecutable() }

/**
* Expects that the subject of `this` expectation (a [Path]) is a file;
* meaning that there is a file system entry at the location the [Path] points to and that is a regular file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
fun0(Expect<Path>::toBeWritable),
fun0(Expect<Path>::notToBeWritable),
fun0(Expect<Path>::toBeExecutable),
fun0(Expect<Path>::notToBeExecutable),
fun0(Expect<Path>::toBeARegularFile),
fun0(Expect<Path>::toBeADirectory),
fun0(Expect<Path>::toBeASymbolicLink),
Expand Down Expand Up @@ -63,6 +64,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
a1.toBeWritable()
a1.notToBeWritable()
a1.toBeExecutable()
a1.notToBeExecutable()
a1.toBeARegularFile()
a1.toBeADirectory()
a1.toBeASymbolicLink()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,6 @@ class PathExpectationSamples {
fails {
expect(readWriteFile).notToBeWritable()
}

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

fails {
Expand All @@ -208,6 +204,35 @@ class PathExpectationSamples {
}
}

@Test
fun notToBeExecutable() {
assertIf(ifPosixSupported) {
val readyOnlyPermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("r--r--r--"))
val readyWriteExecutePermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr--r--"))

val readOnlyDir = tempDir.newDirectory("read_only_dir", readyOnlyPermissions)
val readOnlyFile = tempDir.newFile("read_only_file", readyOnlyPermissions)
val readWriteExecuteDir = tempDir.newDirectory("read_write_execute_dir", readyWriteExecutePermissions)
val readWriteExecuteFile = tempDir.newFile("read_write_execute_file", readyWriteExecutePermissions)

expect(readOnlyDir).notToBeExecutable()
expect(readOnlyFile).notToBeExecutable()

fails {
expect(readWriteExecuteDir).notToBeExecutable()
}
fails {
expect(readWriteExecuteFile).notToBeExecutable()
}
}

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

@Test
fun toBeARegularFile() {
val file = tempDir.newFile("test_file")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,25 @@ infix fun <T : Path> Expect<T>.notToBe(@Suppress("UNUSED_PARAMETER") readable: r
*/
infix fun <T : Path> Expect<T>.notToBe(@Suppress("UNUSED_PARAMETER") writable: writable): Expect<T> =
_logicAppend { isNotWritable() }

/**
* Expects that the subject of `this` expectation (a [Path]) is not executable;
* 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 execute it.
*
* The semantics of “permission to execute it” may differ when checking access to a directory. For example, on UNIX
* systems, it means that the Java virtual machine has permission to search the directory in order to access file or
* subdirectories.
*
* 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.
*
* @return an [Expect] for the subject of `this` expectation.
*
* @sample ch.tutteli.atrium.api.infix.en_GB.samples.PathExpectationSamples.notToBeExecutable
*
* @since 0.17.0
*/
infix fun <T : Path> Expect<T>.notToBe(@Suppress("UNUSED_PARAMETER") executable: executable): Expect<T> =
_logicAppend { isNotExecutable() }
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
"toBe ${writable::class.simpleName}" to Companion::toBeWritable,
"notToBe ${writable::class.simpleName}" to Companion::notToBeWritable,
"toBe ${executable::class.simpleName}" to Companion::toBeExecutable,
"notToBe ${executable::class.simpleName}" to Companion::notToBeExecutable,
"toBe ${aRegularFile::class.simpleName}" to Companion::toBeRegularFile,
"toBe ${aDirectory::class.simpleName}" to Companion::toBeADirectory,
"toBe ${aSymbolicLink::class.simpleName}" to Companion::toBeASymbolicLink,
Expand Down Expand Up @@ -50,6 +51,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
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
private fun notToBeExecutable(expect: Expect<Path>) = expect notToBe executable
private fun toBeRegularFile(expect: Expect<Path>) = expect toBe aRegularFile
private fun toBeADirectory(expect: Expect<Path>) = expect toBe aDirectory
private fun toBeASymbolicLink(expect: Expect<Path>) = expect toBe aSymbolicLink
Expand Down Expand Up @@ -97,6 +99,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
a1 toBe writable
a1 notToBe writable
a1 toBe executable
a1 notToBe executable
a1 toBe aRegularFile
a1 toBe aDirectory
a1 toBe absolute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class PathExpectationSamples {

private val tempDir = Files.createTempDirectory("PathAssertionSamples")

private val ifPosixSupported = fileSystemSupportsPosixPermissions()

@Test
fun toBeASymbolicLink() {
val target = tempDir.newFile("target")
Expand Down Expand Up @@ -160,7 +162,7 @@ class PathExpectationSamples {

@Test
fun notToBeWritable() {
assertIf(fileSystemSupportsPosixPermissions()) {
assertIf(ifPosixSupported) {
val readyOnlyPermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("r--r--r--"))
val readyWritePermissions =
Expand Down Expand Up @@ -198,6 +200,35 @@ class PathExpectationSamples {
}
}

@Test
fun notToBeExecutable() {
assertIf(ifPosixSupported) {
val readyOnlyPermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("r--r--r--"))
val readyWriteExecutePermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr--r--"))

val readOnlyDir = tempDir.newDirectory("read_only_dir", readyOnlyPermissions)
val readOnlyFile = tempDir.newFile("read_only_file", readyOnlyPermissions)
val readWriteExecuteDir = tempDir.newDirectory("read_write_execute_dir", readyWriteExecutePermissions)
val readWriteExecuteFile = tempDir.newFile("read_write_execute_file", readyWriteExecutePermissions)

expect(readOnlyDir) notToBe executable
expect(readOnlyFile) notToBe executable

fails {
expect(readWriteExecuteDir) notToBe executable
}
fails {
expect(readWriteExecuteFile) notToBe executable
}
}

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

@Test
fun toBeARegularFile() {
val file = tempDir.newFile("test_file")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ fun <T : Path> AssertionContainer<T>.isNotReadable(): Assertion = impl.isNotRead
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)
fun <T : Path> AssertionContainer<T>.isNotExecutable(): Assertion = impl.isNotExecutable(this)
fun <T : Path> AssertionContainer<T>.isRegularFile(): Assertion = impl.isRegularFile(this)
fun <T : Path> AssertionContainer<T>.isDirectory(): Assertion = impl.isDirectory(this)
fun <T : Path> AssertionContainer<T>.isSymbolicLink(): Assertion = impl.isSymbolicLink(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface PathAssertions {
fun <T : Path> isWritable(container: AssertionContainer<T>): Assertion
fun <T : Path> isNotWritable(container: AssertionContainer<T>): Assertion
fun <T : Path> isExecutable(container: AssertionContainer<T>): Assertion
fun <T : Path> isNotExecutable(container: AssertionContainer<T>): Assertion
fun <T : Path> isRegularFile(container: AssertionContainer<T>): Assertion
fun <T : Path> isDirectory(container: AssertionContainer<T>): Assertion
fun <T : Path> isSymbolicLink(container: AssertionContainer<T>): Assertion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ class DefaultPathAssertions : PathAssertions {
override fun <T : Path> isExecutable(container: AssertionContainer<T>): Assertion =
filePermissionAssertion(container, EXECUTABLE, AccessMode.EXECUTE, DescriptionBasic.IS, shouldHaveAccess = true)

override fun <T : Path> isNotExecutable(container: AssertionContainer<T>): Assertion =
filePermissionAssertion(
container,
EXECUTABLE,
AccessMode.EXECUTE,
DescriptionBasic.IS_NOT,
shouldHaveAccess = false
)

override fun <T : Path> isRegularFile(container: AssertionContainer<T>): Assertion =
fileTypeAssertion(container, A_FILE) { it.isRegularFile }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ abstract class PathExpectationsSpec(
toBeWritable: Fun0<Path>,
notToBeWritable: Fun0<Path>,
toBeExecutable: Fun0<Path>,
notToBeExecutable: Fun0<Path>,
toBeRegularFile: Fun0<Path>,
toBeADirectory: Fun0<Path>,
toBeASymbolicLink: Fun0<Path>,
Expand Down Expand Up @@ -1017,6 +1018,115 @@ abstract class PathExpectationsSpec(
}
}

describeFun(notToBeExecutable) {
val notToBeExecutableFun = notToBeExecutable.lambda
val expectedMessage = "$isNotDescr: ${EXECUTABLE.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).notToBeExecutableFun()
}.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 executable
checkParentHints = false
) { testFile ->
expect(testFile).notToBeExecutable()
}
}

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

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

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

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

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

it("does not throw for a directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("not-executable"))
folder.whileWithPermissions(OWNER_READ, OTHERS_EXECUTE) {
expect(folder).notToBeExecutable()
}
}
}

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

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

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

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

describeFun(toBeRegularFile) {
val toBeRegularFileFun = toBeRegularFile.lambda
val expectedMessage = "$isDescr: ${A_FILE.getDefault()}"
Expand Down

0 comments on commit fc40766

Please sign in to comment.