[flang-commits] [flang] [llvm] [flang-rt] Honor TMPDIR/TMP/TEMP/TEMPDIR for OPEN(STATUS='SCRATCH') (PR #199517)

via flang-commits flang-commits at lists.llvm.org
Tue Jun 2 01:10:03 PDT 2026


================
@@ -0,0 +1,168 @@
+//===-- unittests/Runtime/ScratchTempDir.cpp --------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Verifies that OPEN(STATUS='SCRATCH') honors the TMPDIR/TMP/TEMP/TEMPDIR
+// environment variables on POSIX, matching
+// llvm::sys::path::system_temp_directory.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _WIN32
+
+#include "CrashHandlerFixture.h"
+#include "gtest/gtest.h"
+#include "flang/Runtime/io-api.h"
+#include "flang/Runtime/iostat-consts.h"
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <optional>
+#include <string>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <utility>
+#include <vector>
+
+using namespace Fortran::runtime;
+using namespace Fortran::runtime::io;
+
+namespace {
+
+// Saves the values of TMPDIR/TMP/TEMP/TEMPDIR on construction and restores
+// them on destruction so a test that mutates them does not leak state to
+// later tests in the same process.
+class TempDirEnvGuard {
+public:
+  TempDirEnvGuard() {
+    for (const char *name : kVars) {
+      if (const char *value{std::getenv(name)}) {
+        saved_.emplace_back(name, std::string{value});
+      } else {
+        saved_.emplace_back(name, std::nullopt);
+      }
+      ::unsetenv(name);
+    }
+  }
+  ~TempDirEnvGuard() {
+    for (const auto &entry : saved_) {
+      if (entry.second) {
+        ::setenv(entry.first, entry.second->c_str(), /*overwrite=*/1);
+      } else {
+        ::unsetenv(entry.first);
+      }
+    }
+  }
+
+private:
+  static constexpr const char *kVars[]{"TMPDIR", "TMP", "TEMP", "TEMPDIR"};
+  std::vector<std::pair<const char *, std::optional<std::string>>> saved_;
+};
+
+// Opens a SCRATCH unit and returns the iostat value from EndIoStatement
+// (IostatOk on success, non-zero on error). EnableHandlers makes the
+// runtime defer I/O errors to EndIoStatement rather than crashing the
+// process.
+int OpenScratchUnit() {
+  Cookie io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
+  IONAME(EnableHandlers)(io, /*hasIoStat=*/true);
+  IONAME(SetStatus)(io, "SCRATCH", 7);
+  IONAME(SetAction)(io, "READWRITE", 9);
+  int unit{-1};
+  IONAME(GetNewUnit)(io, unit);
+  int iostat{IONAME(EndIoStatement)(io)};
+  if (iostat == IostatOk && unit >= 0) {
+    Cookie close{IONAME(BeginClose)(unit, __FILE__, __LINE__)};
+    IONAME(EndIoStatement)(close);
+  }
+  return iostat;
+}
+
+struct ScratchTempDirTests : CrashHandlerFixture {};
+
+// TMPDIR pointing at a non-existent directory must cause OPEN to fail —
+// this proves the env var is actually consulted by the runtime rather
+// than the hardcoded /tmp being used.
+TEST_F(ScratchTempDirTests, TmpdirHonored) {
+  TempDirEnvGuard guard;
+  ::setenv("TMPDIR", "/this/path/does/not/exist/flang-rt-test", 1);
+  EXPECT_NE(OpenScratchUnit(), IostatOk)
+      << "OPEN(STATUS='SCRATCH') should fail when TMPDIR points to a "
+         "non-existent directory";
+}
+
+// With no env vars set, OPEN must succeed via the /tmp fallback. This
+// confirms backward compatibility for users who do not set TMPDIR.
+TEST_F(ScratchTempDirTests, FallbackToTmp) {
+  TempDirEnvGuard guard;
+  EXPECT_EQ(OpenScratchUnit(), IostatOk)
+      << "OPEN(STATUS='SCRATCH') should succeed via /tmp fallback when no "
+         "temp env vars are set";
+}
+
+// TMP must be consulted when TMPDIR is unset. Set TMP to a non-existent
+// path and expect failure.
+TEST_F(ScratchTempDirTests, TmpConsulted) {
+  TempDirEnvGuard guard;
+  ::setenv("TMP", "/this/path/does/not/exist/flang-rt-test", 1);
+  EXPECT_NE(OpenScratchUnit(), IostatOk)
+      << "OPEN(STATUS='SCRATCH') should fail when TMP points to a "
+         "non-existent directory and TMPDIR is unset";
+}
+
+// An empty TMPDIR string must be skipped, so the next env var (TMP) wins.
+// Here TMP is a real directory, so OPEN should succeed.
+TEST_F(ScratchTempDirTests, EmptyTmpdirSkipped) {
+  TempDirEnvGuard guard;
+  char tmpDir[]{"/tmp/flang-rt-scratch-test-XXXXXX"};
+  ASSERT_NE(::mkdtemp(tmpDir), nullptr)
+      << "mkdtemp failed: " << strerror(errno);
+  ::setenv("TMPDIR", "", 1);
+  ::setenv("TMP", tmpDir, 1);
+  EXPECT_EQ(OpenScratchUnit(), IostatOk)
+      << "Empty TMPDIR should be skipped in favor of TMP";
+  ::rmdir(tmpDir);
+}
+
+// SCRATCH file should actually be created inside the user-specified temp
----------------
jeanPerier wrote:

This is a fun test, however given it cannot fail even if the feature is completely broken, I do not think it is useful as such/it is giving a false sense of confidence. That is, `st_mtime` can only increase, regardless of what flang I/O runtime does (well, unless the IO starts writing in the OS part that is dealing with time and then something is really broken^^).

To make it relevant, I think you would need to sleep for a couple second and then require `during.st_mtime` to be strictly greater than before (since st_mtime is in second). But then I would be a bit worried about false negative, I do not know how reliably timestamps are bumped on the different systems. 

https://github.com/llvm/llvm-project/pull/199517


More information about the flang-commits mailing list