【问题标题】:Create a file which cannot be deleted by file.delete()创建一个不能被 file.delete() 删除的文件
【发布时间】:2018-06-12 16:56:57
【问题描述】:

注意:我知道有几个与此类似的问题,但是,我找不到任何解释如何解决我正在尝试解决的情况的问题。我会用一个具体的例子来问这个问题,我需要一个解决方案。

考虑代码:

private final void writeToFile(final File parent, final String filename, final Charset charset, final String content) throws IOException {
    final File file = new File(parent, filename);

    if (file.exists()) {
        LOG.warn("File {} already exists, file will be replaced.", file.getCanonicalPath());
        if (!file.delete()) {
            logAndThrow(String.format("Cannot delete file '%s'.", file.getCanonicalPath()), null);
        }
    }

    try (final FileOutputStream fos = new FileOutputStream(file);
            OutputStreamWriter writer = new OutputStreamWriter(fos, charset)) {
        writer.write(content);
    }
}

我正在尝试编写一个单元测试,以在代码无法删除文件时引发 IOException。我试过的单元测试如下:

@Test public void testFileNotDeletable() throws IOException {
  final File file = new File(folder.getRoot(), formattedFile.getMetaData().getFormattedCaptureFileName());
  file.createNewFile();
  try {
    file.setReadOnly();

    exception.expect(IOException.class);
    exception.expectMessage(String.format("Cannot delete file '%s'.", file.getCanonicalPath()));

    writer.write(formattedFile);
  } finally {
    file.setWritable(true);
  }
}

我也试过锁定文件:

@Test public void testFileNotDeletable() throws IOException {
    final File file = new File(folder.getRoot(), formattedFile.getMetaData().getFormattedCaptureFileName());
    file.createNewFile();
    try (FileInputStream fis = new FileInputStream(file)) {
        final FileLock lock = fis.getChannel().tryLock(0L, Long.MAX_VALUE, true);
        try {
            exception.expect(IOException.class);
            exception.expectMessage(String.format("Cannot delete file '%s'.", file.getCanonicalPath()));

            writer.write(formattedFile);
        } finally {
            lock.release();
        }
    }
}

不管我怎么尝试,file.delete() 成功删除了文件,但测试失败,因为没有抛出预期的 IOException。

非常感谢任何帮助。

注意:为澄清起见,添加了一些额外的代码,表明 File 对象在环境中是完全独立的。传递给 write 方法的 formattedFile 不是 File 或 File 的子类,它是我们的内部类之一。 JUnit 测试中的 File 使用 TemporaryFolder 作为根,formattedFile 有一个 MetaData 项,它确定文件名。在我的 JUnit 测试中,我试图在我的实际代码将尝试写入文件的位置创建一个无法删除的空文件。我需要 file.delete() 返回 false,以便我可以测试是否抛出异常。因此,我无法模拟 File 对象。

【问题讨论】:

  • 模拟对file.delete()的调用。
  • 您使用旧文件 api 而不是围绕FilesPathsPath 旋转的现代 NIO 有什么原因吗?
  • 使用不允许删除的文件权限。
  • 我确实查看了 Files 接口,但是我不想使用引发 IOException 的机制,我想使用已经封装了逻辑的机制,如果成功则简单地返回 true/false . Files.deleteIfExists() 没有给我我想要的。

标签: java file junit4


【解决方案1】:

您的问题有两种解决方案,我推荐第一种。

  • 解决方案 1 您不是在此处测试 java 文件 I/O 操作/类,而是在测试代码的功能行为以响应文件操作。因此,理想情况下,您应该在您的 JUnit 中模拟 File 对象及其各自的调用,并且只专注于测试您的代码。

  • 解决方案 2 如果您仍想测试与 java 文件 IO 的完全集成,请在尝试删除之前以写入模式打开文件,它会处理您的测试用例。

注意:在 CENTOS、WINDOWS、UBUNTU、MAC OS-X 中测试的代码

学科类别:

    public class FileSolution {
        public void fileHandler(File file) throws IOException, Exception {
            if (file.exists()) {
                LOG.warn("File {} already exists, file will be replaced.", 
                        file.getCanonicalPath());
                if (!file.delete()) {
                    logAndThrow(String.format("Cannot delete file '%s'.", 
                            file.getCanonicalPath()),
                            new IOException(String.format("Cannot delete file '%s'.", 
                                    file.getCanonicalPath())));
                }
            }
        }
    }

科目超额测试:

import static org.mockito.BDDMockito.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class FileSolutionTest {

    @Rule
    public final ExpectedException exception = ExpectedException.none();

    /**
     * Solution 1
     * 
     * @throws Exception
     */
    @Test
    public void testFileNotDeletableWithMock() throws Exception {
        final File file = mock(File.class);
        file.createNewFile();
        // mock file & IO operations
        given(file.exists()).willReturn(true);
        given(file.delete()).willReturn(false);
        given(file.getCanonicalPath()).willReturn("test.txt");

        exception.expect(IOException.class);
        exception.expectMessage(String.format("Cannot delete file '%s'.", file.getCanonicalPath()));

        new FileSolution().fileHandler(file);
    }

    /**
     * Solution 2
     * 
     * @throws Exception
     */
    @Test
    public void testFileNotDeletable() throws Exception {
        File file = null;
        FileWriter fileWriter = null;
        try{
            file = new File("test.txt");
            file.createNewFile();
            file.deleteOnExit();
            exception.expect(IOException.class);
            exception.expectMessage(String.format("Cannot delete file '%s'.", file.getCanonicalPath()));
            // open file with another process for writing
            fileWriter = new FileWriter(file, true);
            new FileSolution().fileHandler(file);
        } finally{
            if(fileWriter != null){
                fileWriter.flush();
                fileWriter.close();
            }
        }
    }
}

【讨论】:

  • 有些操作系统允许删除,即使文件在另一个进程中打开。
  • Linux 就是其中之一。在测试中打开和锁定文件,不会阻止 Linux 删除它。
  • 是的,你是对的,因此删除了操作系统特定的评论。
【解决方案2】:

我完全同意 Turing85 关于使用 mockito 的观点。

假设您有一个原始类,其方法类似于您要测试的方法:

public class FileDel {

    public void logOnIOException(File file) throws IOException {
        if (file.exists()) {
            LOG.warn("File {} already exists, file will be replaced.", file.getCanonicalPath());
            if (!file.delete()) {
                logAndThrow(String.format("Cannot delete file '%s'.", file.getCanonicalPath()), null);
            }
        }
    }

    public void logAndThrow(String msg, String s) {
        //Do nothing
    }

    private static class LOG {
        public static void warn(String msg, String path) {
        }
    }
}

那么可以这样触发内部异常:

@RunWith(MockitoJUnitRunner.class)
public class FileDelTest {
    @Test(expected = IOException.class)
    public void testFileNotDeletable() throws IOException {
        File file = mock(File.class);
        when(file.exists()).thenReturn(true);
        when(file.delete()).thenAnswer(new Answer<Boolean>() {
            @Override
            public Boolean answer(InvocationOnMock iom) throws Throwable {
                throw new IOException();
            }
        });

        FileDel f = new FileDel();

        try {
            f.methodToTest(file);
        } finally {
        }
    }
}

【讨论】:

  • 我无法模拟 File 对象,它与单元测试完全分开。我需要标记实际文件,使其无法被删除,但 file.delete() 总是成功(它出现)。
  • 嗨,马库斯。这件事越来越有趣。你想在不同操作系统上为你的应用程序创建一个真实的测试,现有文件真的被锁定了吗?或者更确切地说,您想模拟这种行为(文件删除引发 IOException)但您目前在这个方向上没有成功?
【解决方案3】:

为这个文件打开InputStream 而不关闭它怎么样。直到文件的描述符不会被关闭,文件才会被删除。

【讨论】:

  • Linux 允许删除打开的文件。
【解决方案4】:

为了防止文件被删除,您必须在 Windows 中拒绝安全权限。在 UI 中,我们需要执行类似

的操作
  1. 右键单击 PC 中的文件或文档 => 选择属性;
  2. 在安全中,选项卡编辑以更改权限 => 选择添加并输入所有人;
  3. 按确定并选择组以将完全控制权限更改为拒绝;
  4. 按是确认。

我知道使用 Java 更改文件权限的唯一方法是:

file.setExecutable(true|false);
file.setReadable(true|false);
file.setWritable(true|false);

File file = new File("test.txt");

if(file.exists())
{
    //Setting file permissions for owner, group and others using PosixFilePermission

    HashSet<PosixFilePermission> set = new HashSet<PosixFilePermission>();

    //Adding owner's file permissions

    set.add(PosixFilePermission.OWNER_EXECUTE);
    set.add(PosixFilePermission.OWNER_READ);
    set.add(PosixFilePermission.OWNER_WRITE);

    //Adding group's file permissions

    set.add(PosixFilePermission.GROUP_EXECUTE);
    set.add(PosixFilePermission.GROUP_READ);
    set.add(PosixFilePermission.GROUP_WRITE);

    //Adding other's file permissions

    set.add(PosixFilePermission.OTHERS_EXECUTE);
    set.add(PosixFilePermission.OTHERS_READ);
    set.add(PosixFilePermission.OTHERS_WRITE);

    Files.setPosixFilePermissions(Paths.get("test.txt"), set);
}
else
{
    System.out.println("Sorry...File doesn't exist.");
}

因此,我认为防止文件被删除与文件写入权限有关。在尝试删除文件之前尝试禁用可写权限和可执行权限。

如果这不起作用,那么我不相信它可以用 Java 语言完成,因为这些是目前更改文件权限的唯一可用方法。我可能是错的,但我找不到其他任何东西。

更新

对于 Linux,请尝试以下操作:

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ExecuteShellComand {

    public static void main(String[] args) {

        ExecuteShellComand obj = new ExecuteShellComand();

        String command = "sudo chattr +i /backups/passwd";
        // OR try

        //String command = "sudo chattr +i -V /backups/passwd";

        String output = obj.executeCommand(command);

        System.out.println(output);

    }

    private String executeCommand(String command) {

        StringBuffer output = new StringBuffer();

        Process p;
        try {
            p = Runtime.getRuntime().exec(command);
            p.waitFor();
            BufferedReader reader = 
                            new BufferedReader(new InputStreamReader(p.getInputStream()));

                        String line = "";           
            while ((line = reader.readLine())!= null) {
                output.append(line + "\n");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return output.toString();

    }
}

以上内容使 /backups/passwd 文件不可变(或不可删除)。这意味着文件不能以任何方式修改:不能删除或重命名。您甚至无法创建指向它的链接,也无法将数据写入文件。 这是我唯一能想到的。

希望这会有所帮助。

【讨论】:

  • PC 中的右键单击是什么意思。我正在使用 Linux 进行开发。
  • 将文件权限设置为 000 没有产生任何解决方案。 files.delete() 仍然删除文件。
【解决方案5】:

在 Linux 中,您可以使用 chattr 命令设置一个“不可变”的文件,即使是 root 也无法删除。有人说“设置文件权限”是对的,但没有给出具体细节。

干杯 D

【讨论】:

    猜你喜欢
    • 2020-11-27
    • 1970-01-01
    • 1970-01-01
    • 2014-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-28
    • 1970-01-01
    相关资源
    最近更新 更多