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

[#3007] Content Assistant test infrastructure improvements. #3024

Merged
merged 1 commit into from
May 13, 2024

Conversation

miklossy
Copy link
Contributor

  • Extend the Content Assistant test infrastructure to test proposals from several resources: modify the AbstractContentAssistTest getResourceFor method to ensure that the test file points to a file in the test project.
  • Uplift the testContentAssistant method from the project example classes into the AbstractContentAssistTest and rename it to assertContentAssistant.
  • Add the events_from_another_file() test case to the StatemachineContentAssistTest test cases to demonstrate how to test the content assistant with several resources.
  • Also add the ContentAssistWithSeveralResourcesTest test cases that are continuously executed by the CI build.

Closes #3007

@miklossy miklossy added this to the Release_2.35 milestone Apr 30, 2024
@miklossy
Copy link
Contributor Author

This PR is based on the discussion between @LorenzoBettini and @cdietrich on the Xtext forum:
https://www.eclipse.org/forums/index.php/t/243876/

* A different resource with the URI 'platform:/resource/contentAssistTest/Test....' was already registered.
*/
Resource resource = resourceSet.getResource(resourceUri, false);
if (resource == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

But if the resource is not null, i.e., it exists, the passed input stream is not used to populate it, is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Exactly.

Copy link
Contributor

Choose a reason for hiding this comment

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

So I still don't understand... I was expecting the specified input stream to be taken into consideration and in case replace the current resource... maybe I'm still missing something

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@LorenzoBettini : The method getResourceFor(InputStream stream) will be called twice within each test case execution, in both cases the passed stream is created from the currentModelToParse variable. So basically it does not matter if we use the first call and ingore the second call or if we create the resource on the first call and overwrite it with the same content in the second call.

I modified the PR so that in the second call the current resource content will be replaced, as requested.

Copy link
Contributor

Choose a reason for hiding this comment

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

@miklossy but the question now is why is it called twice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is a good question, but currently I am not sure if we could fix that without any breaking change. Let's analyse it in a follow up issue :-)

Copy link
Contributor

Choose a reason for hiding this comment

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

It is a good question, but currently I am not sure if we could fix that without any breaking change. Let's analyse it in a follow up issue :-)

OK, if it's always been like that :)

*/
protected IFile createDslFile(String fileName, String fileExtension, CharSequence content) {
try {
return IResourcesSetupUtil.createFile(javaProject.getElementName(), fileName, fileExtension, content.toString());
Copy link
Contributor

Choose a reason for hiding this comment

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

it would be useful to specify also the path of the file, e.g., into the "src" directory. Does this allow that? If so, maybe it should be specified in the documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To specity the worskpace relative path, the IResourcesSetupUtil#createFile(String, String) method can be used. I added this hint to the documentation.

Copy link
Contributor

Choose a reason for hiding this comment

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

So, I don't understand the benefit of this new method... again, I'm probably missing something, but why not simply consider the path in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This createDslFile method is basically a shorter way of calling the IResourcesSetupUtil.createFile method. The user can call this method if he/she wants to pass less parameter and does not want to bother with the exception handling. But if that does not fullfill his/her usecase, than he/she can call directly the IResourcesSetupUtil.createFile method instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, but in any case, can the passed fileName consist of a path? I think that's important. I mean, it's important to be able to specify a path, otherwise that method is unusable for Xbase languages where files must be in source folders.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@LorenzoBettini : I extended the javadoc as follows:

@param fileName
	 * 		The name of the file (without the file extension).
	 * 		It is also possible to use project relative path something like: <code>src/foo/bar/Test</code>
	 * 	 	To be able to specify the workspace relative path, use {@link IResourcesSetupUtil#createFile(String, String)} instead.
	 * 

*
* @since 2.35
*/
protected void assertContentAssistant(CharSequence text, List<String> expectedProposals) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we're creating a new API, could we take the chance to allow for expected proposals to be specified also as a string where proposals are separated by a new line? This would allow for easier TDD or test adjustments: it's easier to specify or update a string than to create a list of proposals or update it. Turning the string into a list or array of strings is easy with Java streams. @szarnekow wdyt?

@Test def empty() throws Exception {
'''
«c»
'''.testContentAssistant(#[
'''.assertContentAssistant(#[
Copy link
Contributor

Choose a reason for hiding this comment

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

For example, with the additional API proposed above, here you could do

'''
commands
events
resetEvents
'''

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea @LorenzoBettini ! I updated the PR so that it is also possible to specify the expected proposals as one CharSequence containing the expected proposals separated by new lines.

Resource resource = resourceSet.createResource(resourceUri);
resource.load(stream, null);
String projectFullPath = javaProject.getProject().getFullPath().toString();
URI resourceUri = URI.createPlatformResourceURI(projectFullPath + "/" + "Test." + fileExtensionProvider.getPrimaryFileExtension(), true);
Copy link
Member

Choose a reason for hiding this comment

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

am not sure. dont in java projects. model files need to reside in source folder?

Copy link
Contributor

Choose a reason for hiding this comment

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

Exactly! That's why I was suggesting below to allow for relative path specification.

Copy link
Contributor

Choose a reason for hiding this comment

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

@miklossy I still don't understand this part... see my comment and @cdietrich's one

Copy link
Member

Choose a reason for hiding this comment

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

from my pov this is still open

Copy link
Contributor

Choose a reason for hiding this comment

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

@cdietrich relative paths are allowed, see the other answers. What did you mean?

Copy link
Member

Choose a reason for hiding this comment

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

i mean the default. unfortunately i dont have cappa to try it

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, now maybe I see: the file on which the content assistant is triggered! That's always the same and cannot be created in a specific folder. Is that right @miklossy ? If it's so, then we should allow for some customization? Maybe a variable that can be changed? And maybe the default should be "src/Test"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@LorenzoBettini : good idea! I have just modified the PR as requested by introducing the following method:

	/**
	 * Returns the project relative path where the files should be created.
	 * The default implementation creates them directly in the src folder.
	 * Clients may override.
	 *
	 * @since 2.35 
	 */
	protected String getProjectRelativePath() {
		return "src";
	}

Copy link
Contributor

Choose a reason for hiding this comment

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

@miklossy Thanks!
@cdietrich I think this also fixes your last issue.

Resource resource = resourceSet.createResource(resourceUri);
resource.load(stream, null);
String projectFullPath = javaProject.getProject().getFullPath().toString();
URI resourceUri = URI.createPlatformResourceURI(projectFullPath + "/" + "Test." + fileExtensionProvider.getPrimaryFileExtension(), true);
Copy link
Contributor

Choose a reason for hiding this comment

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

@miklossy I still don't understand this part... see my comment and @cdietrich's one

* A different resource with the URI 'platform:/resource/contentAssistTest/Test....' was already registered.
*/
Resource resource = resourceSet.getResource(resourceUri, false);
if (resource == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

So I still don't understand... I was expecting the specified input stream to be taken into consideration and in case replace the current resource... maybe I'm still missing something

*/
protected IFile createDslFile(String fileName, String fileExtension, CharSequence content) {
try {
return IResourcesSetupUtil.createFile(javaProject.getElementName(), fileName, fileExtension, content.toString());
Copy link
Contributor

Choose a reason for hiding this comment

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

So, I don't understand the benefit of this new method... again, I'm probably missing something, but why not simply consider the path in the first place?

* @since 2.35
*/
protected void assertContentAssistant(CharSequence text, CharSequence expectedProposals) {
assertContentAssistant(text, expectedProposals.toString().split(System.lineSeparator()), null, null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe I opened a Pandora's box... O:)
I'd like to avoid introducing further problems due to d**n line endings in Windows.
Your implementation works when using Xtend template strings but would fail when using Java text blocks or Xtend multi-line strings that are now (#3004) normalized w.r.t. line endings (only Unix EOL is generated).

I'd like this solution to be guarded against different EOLs...

A possible solution would be to first normalize the expectedProposals with only Unix EOL (we have a utility method Strings.toUnixLineSeparator) and then split it only according "\n".

I haven't tried that, but it should work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@LorenzoBettini : I have just modified the PR as requested:

	protected void assertContentAssistant(CharSequence text, CharSequence expectedProposals) {
		String[] expectedProposalsArray = Strings.toUnixLineSeparator(expectedProposals.toString()).split("\n");
		assertContentAssistant(text, expectedProposalsArray, null, null);
	}

	protected void assertContentAssistant(CharSequence text, CharSequence expectedProposals, String proposalToApply, String expectedContent) {
		String[] expectedProposalsArray = Strings.toUnixLineSeparator(expectedProposals.toString()).split("\n");
		assertContentAssistant(text, expectedProposalsArray, proposalToApply, expectedContent);
	}

Copy link
Contributor

Choose a reason for hiding this comment

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

@miklossy Thanks! I feel safer now ;)

Just in case, can't the first method be implemented in terms of the second one, by simply passing text, expectedProposals, null, null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I modified the PR.

*
* @since 2.35
*/
protected void assertContentAssistant(CharSequence text, String[] expectedProposals) {
Copy link
Contributor

Choose a reason for hiding this comment

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

So you removed the version with List?

That could have been useful in Xtend with #[ ... ]. Or would it work anyway thanks to conversion?

Moreover, if we want to use arrays, why not using String... expectedProposals, which would not require the creation of an array when calling this method? Would it conflict with the one accepting a String?

* @since 2.35
*/
protected void assertContentAssistant(CharSequence text, CharSequence expectedProposals, String proposalToApply, String expectedContent) {
assertContentAssistant(text, expectedProposals.toString().split(System.lineSeparator()), proposalToApply, expectedContent);
Copy link
Contributor

Choose a reason for hiding this comment

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

As above, concerning EOL in the passed string.

@Test public void test_with_two_resources() {
createDslFile("types", "type A extends A");
IResourcesSetupUtil.waitForBuild();
assertContentAssistant("import \"types.importuriuitestlanguage\" type B extends " + c, new String[]{"A", "B"});
Copy link
Contributor

Choose a reason for hiding this comment

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

Here, I would call it with "A\nB" to make sure my proposal above about EOLs works as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have just modified the PR as requested.

@@ -56,7 +52,7 @@ class ContentAssistTest extends AbstractContentAssistTest {
entity E {
«c»
}
'''.testContentAssistant(#[
'''.assertContentAssistant(#[
Copy link
Contributor

Choose a reason for hiding this comment

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

OK, so, concerning my comment above about String[] instead of List, this seems to work out of the box with Xtend, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, Xtend automatically converts the actual parameter from array to list (and vice versa) if the formal parameter is a list/array.

@miklossy miklossy force-pushed the miklossy_issue_3007 branch 2 times, most recently from fe3f940 to 75049df Compare May 10, 2024 09:36
@LorenzoBettini
Copy link
Contributor

Looks like GitHub Actions is having bad days lately... the ubuntu build failed with a timeout.

Let's wait for the build to finish and then we re-run the failing jobs, just in case.

Copy link
Contributor

@LorenzoBettini LorenzoBettini left a comment

Choose a reason for hiding this comment

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

@miklossy I'm fine with the PR, but there's still my question pending about the path in the passed fileName

@miklossy
Copy link
Contributor Author

@miklossy I'm fine with the PR, but there's still my question pending about the path in the passed fileName

It is not possible to use absolute path there, but you can use relative path, something like:
src/foo/bar/testfile

@miklossy miklossy force-pushed the miklossy_issue_3007 branch 2 times, most recently from ef2e617 to 4f31128 Compare May 10, 2024 13:01
@LorenzoBettini
Copy link
Contributor

@miklossy I'm fine with the PR, but there's still my question pending about the path in the passed fileName

It is not possible to use absolute path there, but you can use relative path, something like: src/foo/bar/testfile

No no, I meant project relative path.
I see you have updated the documentation, so to me that's fine! :)

Copy link
Contributor

@LorenzoBettini LorenzoBettini left a comment

Choose a reason for hiding this comment

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

Thanks!

@cdietrich wdyt?

- Extend the Content Assistant test infrastructure to test proposals
from several resources: modify the AbstractContentAssistTest
getResourceFor method to ensure that the test file points to a file in
the test project.
- Uplift the testContentAssistant method from the project example
classes into the AbstractContentAssistTest and rename it to
assertContentAssistant.
- Add the events_from_another_file() test case to the
StatemachineContentAssistTest test cases to demonstrate how to test the
content assistant with several resources.
- Also add the ContentAssistWithSeveralResourcesTest test cases that are
continuously executed by the CI build.

Closes #3007

Signed-off-by: miklossy <miklossy@itemis.de>
@miklossy
Copy link
Contributor Author

@LorenzoBettini : do you see why the build is red?

@LorenzoBettini
Copy link
Contributor

@LorenzoBettini : do you see why the build is red?

macOS on GitHub Actions is completely unreliable and slow lately.
I'll try to re-run. If it still fails, I'll still merge this.

@miklossy
Copy link
Contributor Author

Hmm.. The MacOS build is still red, but the windows and the ubuntu builds are green. Could we merge it, @LorenzoBettini ?

@LorenzoBettini LorenzoBettini merged commit 9ad9582 into main May 13, 2024
10 checks passed
@LorenzoBettini LorenzoBettini deleted the miklossy_issue_3007 branch May 13, 2024 16:37
@LorenzoBettini
Copy link
Contributor

Done! Thanks @miklossy .
Do you think it's worth to say something in the release notes?

@miklossy
Copy link
Contributor Author

Yes, see eclipse/xtext-website#43 :-)

Thanks @LorenzoBettini for your entire support on this PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Extend the Content Assistant test infrastructure to test proposals from several resources.
3 participants