java - JUnit4 - 如果使用 docker 运行,如何测试只读/写保护目录
问题描述
我们有一个集成测试设置,用于测试缺少但需要的配置属性的行为。其中一个属性是一个目录,应该将失败的上传写入该目录以供以后重试。此属性的一般行为应该是应用程序甚至不会在违反某些约束时立即启动和失败。
这些属性由 Spring 通过ConfigurationProperties
其中某些我们有一个简单的S3MessageUploadSettings
类来管理
@Getter
@Setter
@ConfigurationProperties(prefix = "s3")
@Validated
public class S3MessageUploadSettings {
@NotNull
private String bucketName;
@NotNull
private String uploadErrorPath;
...
}
在各自的 Spring 配置中,我们现在执行某些验证检查,例如路径是否存在、是否可写和目录,并RuntimeException
在不满足某些断言时抛出相应的 s:
@Slf4j
@Import({ S3Config.class })
@Configuration
@EnableConfigurationProperties(S3MessageUploadSettings.class)
public class S3MessageUploadSpringConfig {
@Resource
private S3MessageUploadSettings settings;
...
@PostConstruct
public void checkConstraints() {
String sPath = settings.getUploadErrorPath();
Path path = Paths.get(sPath);
...
log.debug("Probing path '{}' for existence', path);
if (!Files.exists(path)) {
throw new RuntimeException("Required error upload directory '" + path + "' does not exist");
}
log.debug("Probig path '{}' for being a directory", path);
if (!Files.isDirectory(path)) {
throw new RuntimeException("Upload directory '" + path + "' is not a directoy");
}
log.debug("Probing path '{}' for write permissions", path);
if (!Files.isWritable(path)) {
throw new RuntimeException("Error upload path '" + path +"' is not writable);
}
}
}
我们的测试设置现在看起来像这样:
public class StartupTest {
@ClassRule
public static TemporaryFolder testFolder = new TemporaryFolder();
private static File BASE_FOLDER;
private static File ACCESSIBLE;
private static File WRITE_PROTECTED;
private static File NON_DIRECTORY;
@BeforeClass
public static void initFolderSetup() throws IOException {
BASE_FOLDER = testFolder.getRoot();
ACCESSIBLE = testFolder.newFolder("accessible");
WRITE_PROTECTED = testFolder.newFolder("writeProtected");
if (!WRITE_PROTECTED.setReadOnly()) {
fail("Could not change directory permissions to readonly")
}
if (!WRITE_PROTECTED.setWritable(false)) {
fail("Could not change directory permissions to writable(false)");
}
NON_DIRECTORY = testFolder.newFile("nonDirectory");
}
@Configuration
@Import({
S3MessageUploadSpringConfig.class,
S3MockConfig.class,
...
})
static class BaseContextConfig {
// common bean definitions
...
}
@Configuration
@Import(BaseContextConfig.class)
@PropertySource("classpath:ci.properties")
static class NotExistingPathContextConfig {
@Resource
private S3MessageUploadSettings settings;
@PostConstruct
public void updateSettings() {
settings.setUploadErrorPath(BASE_FOLDER.getPath() + "/foo/bar");
}
}
@Configuration
@Import(BaseContextConfig.class)
@PropertySource("classpath:ci.properties")
static class NotWritablePathContextConfig {
@Resource
private S3MessageUploadSettings settings;
@PostConstruct
public void updateSettings() {
settings.setUploadErrorPath(WRITE_PROTECTED.getPath());
}
}
...
@Configuration
@Import(BaseContextConfig.class)
@PropertySource("classpath:ci.properties")
static class StartableContextConfig {
@Resource
private S3MessageUploadSettings settings;
@PostConstruct
public void updateSettings() {
settings.setUploadErrorPath(ACCESSIBLE.getPath());
}
}
@Test
public void shouldFailStartupDueToNonExistingErrorPathDirectory() {
ApplicationContext context = null;
try {
context = new AnnotationConfigApplicationContext(StartupTest.NotExistingPathContextConfig.class);
fail("Should not have started the context");
} catch (Exception e) {
e.printStackTrace();
assertThat(e, instanceOf(BeanCreationException.class));
assertThat(e.getMessage(), containsString("Required error upload directory '" + BASE_FOLDER + "/foo/bar' does not exist"));
} finally {
closeContext(context);
}
}
@Test
public void shouldFailStartupDueToNonWritablePathDirectory() {
ApplicationContext context = null;
try {
context = new AnnotationConfigApplicationContext(StartupTest.NotWritablePathContextConfig.class);
fail("Should not have started the context");
} catch (Exception e) {
assertThat(e, instanceOf(BeanCreationException.class));
assertThat(e.getMessage(), containsString("Error upload path '" + WRITE_PROTECTED + "' is not writable"));
} finally {
closeContext(context);
}
}
...
@Test
public void shouldStartUpSuccessfully() {
ApplicationContext context = null;
try {
context = new AnnotationConfigApplicationContext(StartableContextConfig.class);
} catch (Exception e) {
e.printStackTrace();
fail("Should not have thrown an exception of type " + e.getClass().getSimpleName() + " with message " + e.getMessage());
} finally {
closeContext(context);
}
}
private void closeContext(ApplicationContext context) {
if (context != null) {
// check and close any running S3 mock as this may have negative impact on the startup of a further context
closeS3Mock(context);
// stop a running Spring context manually as this might interfere with a starting context of an other test
((ConfigurableApplicationContext) context).stop();
}
}
private void closeS3Mock(ApplicationContext context) {
S3Mock s3Mock = null;
try {
if (context != null) {
s3Mock = context.getBean("s3Mock", S3Mock.class);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != s3Mock) {
s3Mock.stop();
}
}
}
}
在本地运行时,一切看起来都很好,并且所有测试都通过了。尽管我们的 CI 在 docker 容器中运行这些测试,并且由于某种原因,更改文件权限似乎最终会导致 NOOP 在方法调用时返回 true,但不会更改文件权限本身的任何内容。
Neiter File.setReadOnly()
,似乎对 docker 容器中的实际文件权限也没有影响File.setWritable(false)
。Files.setPosixFilePermissions(Path, Set<PosixFilePermission>)
我还尝试将目录更改为真实目录,即/root
受/dev/pts
写保护的目录,尽管 CI 运行测试,因为root
这些目录可由应用程序写入,并且测试再次失败。
我还考虑过使用内存中的文件系统(例如 JimFS),但我不确定如何说服测试使用自定义文件系统。AFAIK JimFS 不支持将其声明为默认文件系统所需的构造函数。
当在 docker 容器中运行或成功测试此类目录时,Java 中存在哪些其他可能性来将目录权限更改为只读/写保护?
解决方案
我认为这是由于 JVM 的权限和策略造成的,如果操作系统阻止了您的 JVM 的某些权限,您将无法从您的代码中执行任何操作。
您可以尝试编辑java.policy文件并设置适当的文件权限。
也许这些将是一些将设置写入权限的给定文件,例如:
grant {
permission java.io.FilePermission "/dev/pts/*", "read,write,delete";
};
文档中的更多示例:https ://docs.oracle.com/javase/8/docs/technotes/guides/security/spec/security-spec.doc3.html 。
推荐阅读
- python - 重新保存上传的文件影响文件格式 - 烧瓶
- typescript - window.openDatabase 不是moodle 3.8 Ionic 应用程序中的函数错误
- python - 单击时 Django 显示另一个模型属性
- java - mUserListLayoutManager = new LinearLayoutManager(getApplicationContext(), LinearLayout.VERTICAL, false); 不工作
- windows - 带有西里尔符号的 Qml 奇怪行为
- php - TYPO3 扩展“ke_search”的分页链接缺少一些参数并且无法正常工作。怎么修?
- laravel - 我可以在 laravel 中定义日志文件的结构吗?
- python - 在 DataFrame 中将值从列翻转到行
- excel - 通过变量将工作表或数据从一个工作簿添加到另一个工作簿
- android - Android Studio 不保存 github 访问令牌