text: #include "dirut.h"
  #include "iterator.h"
  
  #include <library/cpp/testing/unittest/registar.h>
  
  #include <util/system/fs.h>
  #include <util/system/file.h>
  #include <util/generic/hash.h>
  #include <util/generic/algorithm.h>
  #include <util/random/mersenne.h>
  
  static TString JoinWithNewline(const TVector<TString>& strings) {
      TStringStream ss;
      for (const auto& string : strings) {
          ss << string << "\n";
      }
      return ss.Str();
  }
  
  class TDirIteratorTest: public TTestBase {
      UNIT_TEST_SUITE(TDirIteratorTest);
      UNIT_TEST(TestIt)
      UNIT_TEST(TestError)
      UNIT_TEST(TestLocal)
      UNIT_TEST(TestSkip)
      UNIT_TEST(TestSort)
      UNIT_TEST_SUITE_END();
  
  private:
      class TDirHier {
      public:
          struct TPath {
              TString Path;
              int Type;
          };
  
          inline void AddFile(const TString& path) {
              Add(path, 0);
          }
  
          inline void AddDir(const TString& path) {
              Add(path, 1);
          }
  
          inline void Add(const TString& path, int type) {
              const TPath p = {
                  path, type};
  
              Add(p);
          }
  
          inline void Add(const TPath& path) {
              switch (path.Type) {
                  case 0:
                      TFile(path.Path, CreateAlways | RdWr);
                      break;
  
                  case 1:
                      MakeDirIfNotExist(path.Path.data());
                      break;
  
                  case 2:
                      ythrow yexception() << "unknown path type";
              }
  
              Paths_.push_back(path);
              Srch_[path.Path] = path;
          }
  
          inline int Type(const TString& path) {
              THashMap<TString, TPath>::const_iterator it = Srch_.find(path);
  
              UNIT_ASSERT(it != Srch_.end());
  
              return it->second.Type;
          }
  
          inline bool Have(const TString& path, int type) {
              return Type(path) == type;
          }
  
          inline ~TDirHier() {
              for (size_t i = 0; i < Paths_.size(); ++i) {
                  NFs::Remove(Paths_[Paths_.size() - i - 1].Path);
              }
          }
  
      private:
          TVector<TPath> Paths_;
          THashMap<TString, TPath> Srch_;
      };
  
      inline void TestLocal() {
          TString dirname("." LOCSLASH_S);
          TDirIterator d(dirname, FTS_NOCHDIR);
          for (auto it = d.begin(); it != d.end(); ++it) {
          }
      }
  
      inline void TestIt() {
          TDirHier hier;
  
          const TString dir = "tmpdir";
          const TDirHier::TPath path = {dir, 1};
  
          hier.Add(path);
  
          for (size_t i = 0; i < 10; ++i) {
              const TString dir1 = dir + LOCSLASH_C + ToString(i);
              const TDirHier::TPath path1 = {dir1, 1};
  
              hier.Add(path1);
  
              for (size_t j = 0; j < 10; ++j) {
                  const TString subdir2 = ToString(j);
                  const TString dir2 = dir1 + LOCSLASH_C + subdir2;
                  const TDirHier::TPath path2 = {dir2, 1};
  
                  hier.Add(path2);
  
                  for (size_t k = 0; k < 3; ++k) {
                      const TString file = dir2 + LOCSLASH_C + "file" + ToString(k);
                      const TDirHier::TPath fpath = {file, 0};
  
                      hier.Add(fpath);
                  }
              }
          }
  
          TDirIterator d(dir);
  
          for (auto it = d.begin(); it != d.end(); ++it) {
              UNIT_ASSERT(hier.Have(it->fts_path, it->fts_info != FTS_F));
          }
      }
  
      inline void TestSkip() {
          TDirHier hier;
  
          const TString dir = "tmpdir";
          const TDirHier::TPath path = {dir, 1};
  
          hier.Add(path);
          hier.AddDir(dir + LOCSLASH_C + "dir1");
          hier.AddDir(dir + LOCSLASH_C + "dir1" + LOCSLASH_C + "dir2");
          //
          // Without skip
          //
          {
              TDirIterator di(dir);
  
              UNIT_ASSERT(di.Next());
              UNIT_ASSERT_EQUAL(TStringBuf(di.Next()->fts_name), "dir1");
              UNIT_ASSERT_EQUAL(TStringBuf(di.Next()->fts_name), "dir2");
              UNIT_ASSERT_EQUAL(TStringBuf(di.Next()->fts_name), "dir2");
              UNIT_ASSERT_EQUAL(TStringBuf(di.Next()->fts_name), "dir1");
              UNIT_ASSERT(di.Next());
              UNIT_ASSERT_EQUAL(di.Next(), nullptr);
          }
          //
          // With skip
          //
          {
              TDirIterator di(dir);
  
              UNIT_ASSERT(di.Next());
              auto ent = di.Next();
              UNIT_ASSERT_EQUAL(TStringBuf(ent->fts_name), "dir1");
              di.Skip(ent);
              UNIT_ASSERT_EQUAL(TStringBuf(di.Next()->fts_name), "dir1");
              UNIT_ASSERT(di.Next());
              UNIT_ASSERT_EQUAL(di.Next(), nullptr);
          }
      }
  
      inline void TestSort() {
          TDirHier dh;
          const TString dir("tmpdir");
  
          //prepare fs
          {
              TMersenne<ui32> rnd;
              const TString prefixes[] = {
                  "a", "b", "xxx", "111", ""};
  
              dh.AddDir(dir);
  
              for (size_t i = 0; i < 100; ++i) {
                  const TString fname = dir + LOCSLASH_C + prefixes[i % Y_ARRAY_SIZE(prefixes)] + ToString(rnd.GenRand());
  
                  dh.AddFile(fname);
              }
          }
  
          TVector<TString> fnames;
  
          {
              TDirIterator d(dir, TDirIterator::TOptions().SetSortByName());
  
              for (auto it = d.begin(); it != d.end(); ++it) {
                  if (it->fts_info == FTS_F) {
                      fnames.push_back(it->fts_name);
                  }
              }
          }
  
          TVector<TString> sorted(fnames);
          Sort(sorted.begin(), sorted.end());
  
          UNIT_ASSERT_VALUES_EQUAL(JoinWithNewline(fnames), JoinWithNewline(sorted));
      }
  
      inline void TestError() {
          try {
              TDirIterator d("./notexistingfilename");
  
              UNIT_ASSERT(false);
          } catch (const TDirIterator::TError&) {
          } catch (...) {
              UNIT_ASSERT(false);
          }
  
          UNIT_ASSERT(true);
      }
  };
  
  UNIT_TEST_SUITE_REGISTRATION(TDirIteratorTest);
  