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

InstructorCourseDetailsPageUiTest: testing on live server: increase success rate for checking email delivery #7788 #8862

Merged
merged 12 commits into from
Jul 2, 2018

Conversation

tshradheya
Copy link
Member

Fixes #7788

Outline of Solution

  • Used retry manager instead of constant ThreadHelper.wait(5000)
    I haven't tested on live server.

@tshradheya tshradheya added the s.ToReview The PR is waiting for review(s) label Jun 1, 2018
String keyReceivedInEmail =
EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId);
return keyToSend.equals(keyReceivedInEmail);
RetryManager RM = new RetryManager(3);
Copy link
Member Author

Choose a reason for hiding this comment

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

Unsure if I should still keep this as 5 seconds

Copy link
Contributor

@LiHaoTan LiHaoTan Jun 2, 2018

Choose a reason for hiding this comment

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

Let's keep it at 5 seconds. Part of the reason RetryManager wasn't used at the time was that it only supports exponential backoff. However, emails can take few seconds to a minute to deliver. Using exponential backoff can cause the wait time to explode.

Ideally, we should eventually improve RetryManager to be more flexible, i.e. allow for linear to exponential backoff.

In any case, it's probably still better to make a single test retry (albeit slowly) than to restart the entire test.


So I suppose there's a separate problem of our UI tests requiring that many tests require on the previous state of the other.

So I guess I would just talk about it here:
Making individual UI tests depend on the state of the previous is not a good idea but we did it anyway to reduce the time for testing.

However, the problems it caused is worse:

  • Developers either have to read the entire UI test class to modify tests properly
  • Or just read a small part (risky!) and make a change

And because of the second point which is common, it has caused a number of problems in tests and sometimes the unexpected previous state is problematic.

If a developer modify a test before another, it changes the previous state for many other tests that depend on it, which usually don't cause problems but sometimes it breaks other tests but what's worse is it sometimes hides(!!) a problem in another test.

It would be a lot of work to decouple the states and it would probably increase test time by a lot but I think it is probably what we should aim for. Eventually, as we start to be able to create more unit and integration tests, we can get rid of many tests that should not be testing at the UI level. I think we should also use an automated testing platform like Sauce Labs (Not sure if we are applicable for their open source license though) and it can really help in debugging UI tests.

RIght now if we cannot reproduce the UI tests locally, on AppVeyor we have to create a remote session to view the UI test and on Travis we don't even have a GUI so we probably have to resort to different heuristics like generating HTML files and making educated guesses.

Also we may want to use Sauce Labs to run cross browser tests, which is better because we sometimes write tests that are flaky only because they are not written properly but if we enforce that tests must work across browsers it can encourage writing less flaky tests.

@tshradheya tshradheya requested a review from LiHaoTan June 1, 2018 06:23
Copy link
Contributor

@LiHaoTan LiHaoTan left a comment

Choose a reason for hiding this comment

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

Is there a way to use RetryableTaskReturns<String> instead?

What we want to retrieve is keyReceivedInEmail and not a boolean?

Also, can we change listMessagesResponse = service.users().messages().list(username).setMaxResults(5L) .setQ("is:UNREAD").execute();

to not just retrieve the last 5 emails only?

@Override
public Boolean run() throws RuntimeException {

keyToSend = BackDoor.getEncryptedKeyForStudent(courseId, studentEmail);
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this constant? Why is there a need to put this in run()?

Copy link
Member Author

Choose a reason for hiding this comment

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

@LiHaoTan If not just last 5 then how many?

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's fetch everything?

@LiHaoTan LiHaoTan self-assigned this Jun 3, 2018
@LiHaoTan LiHaoTan added s.Ongoing The PR is being worked on by the author(s) and removed s.ToReview The PR is waiting for review(s) labels Jun 3, 2018
@tshradheya tshradheya added s.ToReview The PR is waiting for review(s) and removed s.Ongoing The PR is being worked on by the author(s) labels Jun 4, 2018
@tshradheya tshradheya requested a review from LiHaoTan June 4, 2018 08:41
Copy link
Contributor

@LiHaoTan LiHaoTan left a comment

Choose a reason for hiding this comment

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

We shouldn't handle exceptions like that.

EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId);
RetryManager retryManager = new RetryManager(5);

String keyReceivedInEmail = retryManager.runUntilSuccessful(new RetryableTaskReturns<String>("Received email") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Retrieve registration key is more accurate?

ThreadHelper.waitFor(5000); //TODO: remove this and use RetryManager with a shorter wait interval
String keyReceivedInEmail =
EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId);
RetryManager retryManager = new RetryManager(5);
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add a // TODO: Use linear backoff first before exponential backoff.

keyReceivedInEmail =
EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId);
isDoneExecuting = true;
} catch (Exception e) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We shouldn't catch all the exceptions like that. Also, these exceptions are unexpected, which means there's some kind of problem but we can't do anything useful so we shouldn't be catching them.

Returning EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId); is enough.

boolean isDoneExecuting;

@Override
public String run() throws RuntimeException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's throw the specific exceptions here.

Copy link
Member Author

@tshradheya tshradheya Jun 11, 2018

Choose a reason for hiding this comment

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

This isn't possible cause run() is an inherited method and changing definition function will cause many irrelevant changes in other places.

I have implemented in a different way.

Copy link
Contributor

Choose a reason for hiding this comment

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

We can use RetryableTaskReturnsThrows?


@Override
public boolean isSuccessful(String result) {
return isDoneExecuting;
Copy link
Contributor

Choose a reason for hiding this comment

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

The condition should just be result != null.

@@ -269,12 +270,39 @@ private InstructorCourseDetailsPage getCourseDetailsPage() {

private boolean hasStudentReceivedReminder(String courseName, String courseId, String studentEmail)
throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's throw the specific exceptions here.

// unexpectedly if there are 5 additional emails received excluding the one from TEAMMATES.
listMessagesResponse = service.users().messages().list(username).setMaxResults(5L)
.setQ("is:UNREAD").execute();
// Fetch all emails received by user.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment is not very useful because it is just repeating what the following code is doing.

I suppose the problem is that each method called should be on a separate line and getRegistrationKeyFromGmail will be better as non-static.

Copy link
Member Author

Choose a reason for hiding this comment

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

But EmailAccount.java is a utility class, so making it non-static doesn't make sense

Copy link
Contributor

Choose a reason for hiding this comment

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

Except that EmailAccount has state such as username which while immutable is used in many parts of the code.

@LiHaoTan LiHaoTan added s.Ongoing The PR is being worked on by the author(s) and removed s.ToReview The PR is waiting for review(s) labels Jun 10, 2018
@tshradheya tshradheya added s.ToReview The PR is waiting for review(s) and removed s.Ongoing The PR is being worked on by the author(s) labels Jun 25, 2018
@tshradheya tshradheya requested a review from LiHaoTan June 25, 2018 04:16
Copy link
Contributor

@LiHaoTan LiHaoTan left a comment

Choose a reason for hiding this comment

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

Just one issue

@@ -272,24 +271,18 @@ private InstructorCourseDetailsPage getCourseDetailsPage() {
}

private boolean hasStudentReceivedReminder(String courseName, String courseId, String studentEmail)
throws MaximumRetriesExceededException {
throws Throwable {
Copy link
Contributor

@LiHaoTan LiHaoTan Jul 1, 2018

Choose a reason for hiding this comment

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

Use Exception? TEAMMATES is kinda stuck with it because of some not so good design of mixing exceptions with errors. Some errors in Java are pretty much unrecoverable and we can't do anything useful with it such as OutOfMemmoryError.

@tshradheya
Copy link
Member Author

@LiHaoTan, Made necessary changes
Ready for review

@tshradheya tshradheya requested a review from LiHaoTan July 2, 2018 02:57
@LiHaoTan LiHaoTan added s.FinalReview The PR is ready for final review and removed s.ToReview The PR is waiting for review(s) labels Jul 2, 2018
@LiHaoTan LiHaoTan requested a review from damithc July 2, 2018 04:15
@damithc damithc added s.ToMerge The PR is approved by all reviewers including final reviewer; ready for merging and removed s.FinalReview The PR is ready for final review labels Jul 2, 2018
@tshradheya tshradheya merged commit 444c092 into TEAMMATES:master Jul 2, 2018
@tshradheya tshradheya added the e.2 label Jul 2, 2018
@tshradheya tshradheya added this to the V6.7.0 milestone Jul 2, 2018
@xpdavid xpdavid added the c.Task Other non-user-facing works, e.g. refactoring, adding tests label Jul 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c.Task Other non-user-facing works, e.g. refactoring, adding tests s.ToMerge The PR is approved by all reviewers including final reviewer; ready for merging
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants