Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #4156 - Support nested subfolders in approved Measure directories #4417

Merged
merged 21 commits into from
Oct 5, 2021

Conversation

jmarrec
Copy link
Collaborator

@jmarrec jmarrec commented Aug 25, 2021

Pull request overview

Fix #4156 - Support nested subfolders in approved Measure directories

  • (green) is what is now supported in addition to what was there. ! (orange) is what is still disallowed
 .
 ├── ./docs
+│   ├── ./docs/docs.rb
 │   ├── ./docs/.gitkeep
 │   └── ./docs/subfolder
+│       └── ./docs/subfolder/subfolder_file.txt
 ├── ./.git
!│   └── ./.git/index
 ├── ./.hidden_folder
!│   └── ./.hidden_folder/file.txt
 ├── ./LICENSE.md
 ├── ./measure.rb
 ├── ./measure.xml
+├── ./README.md
 ├── ./README.md.erb
 ├── ./resources
+│   ├── ./resources/resources.rb
 │   └── ./resources/subfolder
+│       └── ./resources/subfolder/subfolder_file.txt
!├── ./root_file.txt
 ├── ./subfolder
!│   └── ./subfolder/subfolder.txt
 └── ./tests
     ├── ./tests/example_model.osm
     ├── ./tests/output
!    │   └── ./tests/output/output.txt
     ├── ./tests/subfolder
+    │   └── ./tests/subfolder/subfolder_file.txt
     ├── ./tests/test_recursive_measure_test.rb
+    └── ./tests/tests.rb

TODO

  • TODO: do we keep ignoring the docs/ directory when we clone the measure?
  • TODO: should BCLMeasure(const openstudio::path&) call checkForUpdatesFiles? Should BCLMeasure::clone(const openstudio::path&) call checkForUpdatesFiles() first so we have a clean BCLXML to use for copying the files?

Pull Request Author

  • BCL API Changes / Additions
  • BCL API methods are tested (in src/utilities/test)
  • All new and existing tests passes
  • If methods have been deprecated, update rest of code to use the new methods

Labels:

  • If change to an IDD file, add the label IDDChange
  • If breaking existing API, add the label APIChange
  • If deemed ready, add label Pull Request - Ready for CI so that CI builds your PR

Review Checklist

This will not be exhaustively relevant to every PR.

  • Perform a Code Review on GitHub
  • Code Style, strip trailing whitespace, etc.
  • All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
  • Labeling is ok
  • If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
  • If feature, test running new feature, try creative ways to break it
  • CI status: all green or justified

@jmarrec jmarrec added the Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge. label Aug 25, 2021
@jmarrec jmarrec requested review from axelstudios and tijcolem and removed request for axelstudios August 25, 2021 13:43
@jmarrec jmarrec self-assigned this Aug 25, 2021
Comment on lines +38 to +40
BCLFileReference::BCLFileReference(const openstudio::path& measureRootDir, const openstudio::path& relativePath, const bool setMembers)
: m_measureRootDir(openstudio::filesystem::system_complete(measureRootDir)),
m_path(openstudio::filesystem::system_complete(measureRootDir / relativePath)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API break. I have to somehow track the measureRootDir so I can change filename() below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was discussed on Slack, and I agree with @tijcolem that we can probably be a little more tolerant of occasional API changes in BCL, compared to the Model API. I'd recommend merge.

Comment on lines +90 to +100
std::string usageType = this->usageType();
openstudio::path baseDir = m_measureRootDir;
if (usageType == "doc") {
baseDir /= "docs";
} else if (usageType == "resource") {
baseDir /= "resources";
} else if (usageType == "test") {
baseDir /= "tests";
}

return toString(openstudio::filesystem::relative(m_path, baseDir));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actual required change for API break above

Comment on lines +66 to +74
static constexpr std::array<std::pair<std::string_view, std::string_view>, 4> rootToUsageTypeMap{{
// fileName, usageType
{"measure.rb", "script"},
{"LICENSE.md", "license"},
{"README.md", "readme"},
{"README.md.erb", "readmeerb"}
// ".gitkeep" // assuming .gitkeep outside a subfolder makes zero sense...
// "measure.xml" // not included in itself!
}};
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved rootFiles (and their automatic usageType) are declared in this single location

Comment on lines +76 to +82
static constexpr std::array<std::pair<std::string_view, std::string_view>, 3> approvedSubFolderToUsageMap{{
{"docs", "doc"},
{"tests", "test"},
{"resources", "resource"},
}};

static constexpr std::array<std::string_view, 1> ignoredSubFolders{"tests/output"};
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved subfolders (and their automatic usageType) are declared in this single location, along with exclusions (one right now) to ingore the result of running openstudio measure --run-tests .

Comment on lines +84 to +85
// TODO: do we want to keep ignoring the docs/ directory?
static constexpr std::array<std::string_view, 1> usageTypesIgnoredOnClone{"doc"};
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • TODO: do we keep ignoring the docs/ directory when we clone the measure?

Comment on lines 1008 to 1009
// TODO: The logic here is weird. why are we creating then removing it?
// removeDirectory(newDir);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// TODO: The logic here is weird. why are we creating then removing it?
// removeDirectory(newDir);

Comment on lines 1011 to 1033
// TODO: isn't it better to copy only what's inside measure.xml?
// The only caveat is what if the measure.xml is outdated? As a reminder, The BCL measure wouldn't have been loaded first, which updates the XML
// So the only potential issue is if the user loads the measure (in IRB/PRY for eg), then continues to make changes to the filesystem, then much
// later calls measure.clone. I don't think this is an issue

openstudio::path tests = toPath("tests");
if (exists(this->directory() / tests) && !this->copyDirectory(this->directory() / tests, newDir / tests)) {
return boost::none;
}
// Copy the measure.xml
openstudio::filesystem::copy_file_no_throw(m_directory / openstudio::path{"measure.xml"}, newDir / openstudio::path{"measure.xml"});

// Then copy whatever is referneced in the measure.xml
for (const BCLFileReference& file : this->files()) {

openstudio::path resources = toPath("resources");
if (exists(this->directory() / resources) && !this->copyDirectory(this->directory() / resources, newDir / resources)) {
return boost::none;
if (std::find_if(usageTypesIgnoredOnClone.cbegin(), usageTypesIgnoredOnClone.cend(),
[&file](const auto& sv) { return file.usageType() == std::string(sv); })
== usageTypesIgnoredOnClone.cend()) {

// BCLFileReference::path() is absolute
openstudio::path oriPath = file.path();
openstudio::path relativePath = openstudio::filesystem::relative(oriPath, m_directory);
openstudio::path destination = newDir / relativePath;
// Create parent directories in destination if need be
openstudio::filesystem::create_directories(destination.parent_path());
openstudio::filesystem::copy_file_no_throw(oriPath, destination);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a change. Instead of relying on the files on disk, we should just rely on the fact that we've got a clean BCLXML... My comment is wrong though, checkForUpdatesFiles isn't called... perhaps we should just do that?

  • TODO: clone should call checkForUpdatesFiles() first?

Comment on lines +107 to +109
path toPath(std::string_view s) {
return toPath(std::string(s));
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new toPath for std::string_view

Comment on lines +294 to +327
TEST_F(BCLFixture, isApprovedFile) {

EXPECT_TRUE(BCLMeasure::isIgnoredFileName(".hello"));
EXPECT_TRUE(BCLMeasure::isIgnoredFileName(""));
EXPECT_TRUE(BCLMeasure::isIgnoredFileName("."));
EXPECT_TRUE(BCLMeasure::isIgnoredFileName(".."));
EXPECT_FALSE(BCLMeasure::isIgnoredFileName(".gitkeep"));
EXPECT_FALSE(BCLMeasure::isIgnoredFileName("hello"));

openstudio::path measureDir = "/usr/output/my_measure";

std::vector<std::pair<openstudio::path, bool>> tests{
{fs::path("measure.rb"), true},
{fs::path("somethingelse.rb"), false}, // Non approved root file
{fs::path("docs/hello.rb"), true},
{fs::path("docs/subfolder/hello.rb"), true},
{fs::path("tests/hello.rb"), true},
{fs::path("tests/subfolder/hello.rb"), true},
{fs::path("tests/output/hello.rb"), false}, // excluded subfolder
{fs::path("resources/hello.rb"), true},
{fs::path("resources/subfolder/hello.rb"), true},
{fs::path("resources/output/hello.rb"), true},

// Not an approved subfolder
{fs::path("non_approved_subfolder/hello.rb"), false},
{fs::path("non_approved_subfolder/subfolder/hello.rb"), false},
{fs::path("non_approved_subfolder/output/hello.rb"), false},
};

for (const auto& [relativeFilePath, expectedAllowed] : tests) {
openstudio::path absoluteFilePath = measureDir / relativeFilePath;
EXPECT_EQ(expectedAllowed, BCLMeasure::isApprovedFile(absoluteFilePath, measureDir));
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test isApprovedFile

}
}

TEST_F(BCLFixture, 4156_TestRecursive) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plenty of more testing, it should be commented enough, so I won't bother adding review comments here. It's quite long too.

@jmarrec jmarrec added the APIChange A motivated non-backward compatible change that breaks the existing API and needs to be communicated label Aug 25, 2021
@tijcolem
Copy link
Collaborator

@jmarrec This looks good but I see one failing unit test

The following tests FAILED:
	651 - BCLFixture.BCLMeasure (Failed)

I built this on my local mac as well and confirm that it is failing. On thing that stands is the number of files in an equality test. 6 vs 5.

/Users/tcoleman/git/OpenStudio/src/utilities/bcl/test/BCLMeasure_GTest.cpp:91: Failure
652: Expected equality of these values:
652:   6u
652:     Which is: 6
652:   measure2->files().size()
652:     Which is: 5

Would you mind looking at this failure? Otherwise I think this good to go.

if (m_bclXML.hasFile(srcItemPath) || (xmlPath == openstudio::filesystem::system_complete(srcItemPath))) {
if (!openstudio::filesystem::copy_file_no_throw(srcItemPath, dstItemPath)) {
return false;
if (parentPath == measureDir) {
Copy link
Collaborator Author

@jmarrec jmarrec Sep 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the unit test is failing @tijcolem .

Once again I got bit by the fact that toPath("/path/to/dir/") != toPath("/path/to/dir") in boost::filesystem::path...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Path handling is absolutely infuriating in C++, whether that's boost::filesystem or std::filesystem)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5eff870 should have fixed this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmarrec Thanks for addressing this. That is supper annoying that you can't compare equality using == with either of those. It looks like your last commit cleared up Mac and Linux but of course Windows is being finicky and still failing on the same test. I tried to launch a full build to make sure, but yep, still failing.

https://ci.commercialbuildings.dev/blue/organizations/jenkins/openstudio-develop-nightly/detail/openstudio-develop-nightly/1241/pipeline/280

Comment on lines +1008 to +1020
// TODO: isn't it better to copy only what's inside measure.xml?
constexpr bool weCareAboutOutdatedMeasureXML = true;
// The only caveat is what if the measure.xml is outdated with regards to <files>?
// Currently the BCLMeasure(const path&) ctor **does NOT** call checkUpdatesFiles()
//
// We could call it here by that would require a const_cast and it would increment the measure.xml versionId of the original measure,
// and update the list of files, which isn't a big deal if the user doesn't then save it too? Even if the user did it, wouldn't we be helping out anyways?

// If this is really not wanted, we can go back to scanning the files on disk, with a similar logic as in checkForUpdatesFiles:
// loop on all files on disk, and if isApproved is true, then copy them

// Copy the measure.xml
openstudio::filesystem::copy_file_no_throw(m_directory / openstudio::path{"measure.xml"}, newDir / openstudio::path{"measure.xml"});
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmarrec jmarrec force-pushed the 4156_Recursive_Measure_Folder branch from 1d35fa7 to 5eff870 Compare September 21, 2021 14:35
@tijcolem tijcolem merged commit 81c7067 into develop Oct 5, 2021
@tijcolem tijcolem deleted the 4156_Recursive_Measure_Folder branch October 5, 2021 02:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
APIChange A motivated non-backward compatible change that breaks the existing API and needs to be communicated component - Measure Manager component - Utilities Other Enhancement Request Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Recursive copy of the resources/ subfolder of a measure
4 participants